Commit 28181671 authored by charity-sock-pacifist's avatar charity-sock-pacifist Committed by GitHub

feat: swap fees [main] (#7478)

parent c2440d10
import { CurrencyAmount } from '@uniswap/sdk-core'
import { FeatureFlag } from 'featureFlags'
import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getBalance, getTestSelector } from '../../utils'
describe('Swap with fees', () => {
describe('Classic swaps', () => {
beforeEach(() => {
cy.visit('/swap', { featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }] })
// Store trade quote into alias
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
// Avoid tracking stablecoin pricing fetches
if (JSON.parse(req.body).intent !== 'pricing') req.alias = 'quoteFetch'
})
})
it('displays $0 fee on swaps without fees', () => {
// Set up a stablecoin <> stablecoin swap (no fees)
cy.get('#swap-currency-input .open-currency-select-button').click()
cy.contains('DAI').click()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').type('1')
// Verify 0 fee UI is displayed
cy.get(getTestSelector('swap-details-header-row')).click()
cy.contains('Fee')
cy.contains('$0')
})
it('swaps ETH for USDC exact-out with swap fee', () => {
cy.hardhat().then((hardhat) => {
getBalance(USDC_MAINNET).then((initialBalance) => {
// Set up swap
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .token-amount-input').type('1')
cy.wait('@quoteFetch')
.its('response.body')
.then(({ quote: { portionBips, portionRecipient, portionAmount } }) => {
// Fees are generally expected to always be enabled for ETH -> USDC swaps
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
if (portionRecipient) return
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
const feeCurrencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, portionAmount)
// Initiate transaction
cy.get('#swap-button').click()
cy.contains('Review swap')
// Verify fee percentage and amount is displayed
cy.contains(`Fee (${portionBips / 100}%)`)
// Confirm transaction
cy.contains('Confirm swap').click()
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify the post-fee output is the expected exact-out amount
const finalBalance = initialBalance + 1
cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`)
getBalance(USDC_MAINNET).should('eq', finalBalance)
// Verify fee recipient received fee
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
const expectedFinalRecipientBalance = initialRecipientBalance.add(feeCurrencyAmount)
cy.then(() => finalRecipientBalance.equalTo(expectedFinalRecipientBalance)).should('be.true')
})
})
})
})
})
})
it('swaps ETH for USDC exact-in with swap fee', () => {
cy.hardhat().then((hardhat) => {
// Set up swap
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-input .token-amount-input').type('.01')
cy.wait('@quoteFetch')
.its('response.body')
.then(({ quote: { portionBips, portionRecipient } }) => {
// Fees are generally expected to always be enabled for ETH -> USDC swaps
// If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars
if (portionRecipient) return
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => {
// Initiate transaction
cy.get('#swap-button').click()
cy.contains('Review swap')
// Verify fee percentage and amount is displayed
cy.contains(`Fee (${portionBips / 100}%)`)
// Confirm transaction
cy.contains('Confirm swap').click()
// Verify transaction
cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending')
cy.get(getTestSelector('popups')).contains('Swapped')
// Verify fee recipient received fee
cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => {
cy.then(() => finalRecipientBalance.greaterThan(initialRecipientBalance)).should('be.true')
})
})
})
})
})
})
describe('UniswapX swaps', () => {
it('displays UniswapX fee in UI', () => {
cy.visit('/swap', {
featureFlags: [
{ name: FeatureFlag.feesEnabled, value: true },
{ name: FeatureFlag.uniswapXDefaultEnabled, value: true },
],
})
// Intercept the trade quote
cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => {
// Avoid intercepting stablecoin pricing fetches
if (JSON.parse(req.body).intent !== 'pricing') {
req.reply({ fixture: 'uniswapx/feeQuote.json' })
}
})
// Setup swap
cy.get('#swap-currency-input .open-currency-select-button').click()
cy.contains('USDC').click()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('ETH').click()
cy.get('#swap-currency-input .token-amount-input').type('200')
// Verify fee UI is displayed
cy.get(getTestSelector('swap-details-header-row')).click()
cy.contains('Fee (0.15%)')
})
})
})
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "200000000",
"endAmount": "200000000"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "123803169993201727",
"endAmount": "117908377342236273",
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "185983730585681",
"endAmount": "177128258400955",
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
},
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
"startTimeBufferSecs": 45,
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x0bebc200"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0x01b7d653c183183f"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x01a2e50b6386d671"
},
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0xa926b6321051"
},
"endAmount": {
"type": "BigNumber",
"hex": "0xa118e2ebf2bb"
},
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
}
}
},
"portionBips": 15,
"portionAmount": "185983730585681",
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
},
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"allQuotes": [
{
"routing": "DUTCH_LIMIT",
"quote": {
"orderInfo": {
"chainId": 1,
"permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3",
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633",
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x",
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": "100",
"input": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"startAmount": "200000000",
"endAmount": "200000000"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "123803169993201727",
"endAmount": "117908377342236273",
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": "185983730585681",
"endAmount": "177128258400955",
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
},
"encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327",
"quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9",
"startTimeBufferSecs": 45,
"auctionPeriodSecs": 60,
"deadlineBufferSecs": 12,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 1,
"verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
},
"types": {
"PermitWitnessTransferFrom": [
{
"name": "permitted",
"type": "TokenPermissions"
},
{
"name": "spender",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "witness",
"type": "ExclusiveDutchOrder"
}
],
"TokenPermissions": [
{
"name": "token",
"type": "address"
},
{
"name": "amount",
"type": "uint256"
}
],
"ExclusiveDutchOrder": [
{
"name": "info",
"type": "OrderInfo"
},
{
"name": "decayStartTime",
"type": "uint256"
},
{
"name": "decayEndTime",
"type": "uint256"
},
{
"name": "exclusiveFiller",
"type": "address"
},
{
"name": "exclusivityOverrideBps",
"type": "uint256"
},
{
"name": "inputToken",
"type": "address"
},
{
"name": "inputStartAmount",
"type": "uint256"
},
{
"name": "inputEndAmount",
"type": "uint256"
},
{
"name": "outputs",
"type": "DutchOutput[]"
}
],
"OrderInfo": [
{
"name": "reactor",
"type": "address"
},
{
"name": "swapper",
"type": "address"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
},
{
"name": "additionalValidationContract",
"type": "address"
},
{
"name": "additionalValidationData",
"type": "bytes"
}
],
"DutchOutput": [
{
"name": "token",
"type": "address"
},
{
"name": "startAmount",
"type": "uint256"
},
{
"name": "endAmount",
"type": "uint256"
},
{
"name": "recipient",
"type": "address"
}
]
},
"values": {
"permitted": {
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": {
"type": "BigNumber",
"hex": "0x0bebc200"
}
},
"spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"witness": {
"info": {
"reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4",
"swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F",
"nonce": {
"type": "BigNumber",
"hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401"
},
"deadline": 1697481666,
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x"
},
"decayStartTime": 1697481594,
"decayEndTime": 1697481654,
"exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb",
"exclusivityOverrideBps": {
"type": "BigNumber",
"hex": "0x64"
},
"inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"inputStartAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"inputEndAmount": {
"type": "BigNumber",
"hex": "0x0bebc200"
},
"outputs": [
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0x01b7d653c183183f"
},
"endAmount": {
"type": "BigNumber",
"hex": "0x01a2e50b6386d671"
},
"recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F"
},
{
"token": "0x0000000000000000000000000000000000000000",
"startAmount": {
"type": "BigNumber",
"hex": "0xa926b6321051"
},
"endAmount": {
"type": "BigNumber",
"hex": "0xa118e2ebf2bb"
},
"recipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
]
}
}
},
"portionBips": 15,
"portionAmount": "185983730585681",
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327"
}
},
{
"routing": "CLASSIC",
"quote": {
"methodParameters": {
"calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d85d0000000000000000000000000000000000000000000000000000000000000000308060c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000001bdf1285753b47400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000037a8f295612602f2774d331e562be9e61b83a327000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000001bd45ea74e458eb",
"value": "0x00",
"to": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD"
},
"blockNumber": "18364784",
"amount": "200000000",
"amountDecimals": "200",
"quote": "126149127803342909",
"quoteDecimals": "0.126149127803342909",
"quoteGasAdjusted": "122888348391508943",
"quoteGasAdjustedDecimals": "0.122888348391508943",
"quoteGasAndPortionAdjusted": "122699124699803928",
"quoteGasAndPortionAdjustedDecimals": "0.122699124699803928",
"gasUseEstimateQuote": "3260779411833966",
"gasUseEstimateQuoteDecimals": "0.003260779411833966",
"gasUseEstimate": "240911",
"gasUseEstimateUSD": "5.153332510477604328",
"simulationStatus": "SUCCESS",
"simulationError": false,
"gasPriceWei": "13535203506",
"route": [
[
{
"type": "v2-pool",
"address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
"tokenIn": {
"chainId": 1,
"decimals": "6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC"
},
"tokenOut": {
"chainId": 1,
"decimals": "18",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH"
},
"reserve0": {
"token": {
"chainId": 1,
"decimals": "6",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"symbol": "USDC"
},
"quotient": "27487668611269"
},
"reserve1": {
"token": {
"chainId": 1,
"decimals": "18",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"symbol": "WETH"
},
"quotient": "17390022942803382004255"
},
"amountIn": "200000000",
"amountOut": "125959904111637894"
}
]
],
"routeString": "[V2] 100.00% = USDC -- [0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc] --> WETH",
"quoteId": "f46cf31c-251e-470c-bd57-13209015694e",
"portionBips": 15,
"portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327",
"portionAmount": "189223691705014",
"portionAmountDecimals": "0.000189223691705014",
"requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f",
"tradeType": "EXACT_INPUT",
"slippage": 0.5
}
}
]
}
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"incremental": true, "incremental": true,
"isolatedModules": false, "isolatedModules": false,
"noImplicitAny": false, "noImplicitAny": false,
"target": "ES5", "target": "ES6",
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo "tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo
"types": ["cypress", "node"], "types": ["cypress", "node"],
}, },
......
...@@ -18,6 +18,7 @@ import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefaul ...@@ -18,6 +18,7 @@ import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefaul
import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput'
import { useUniswapXExactOutputFlag } from 'featureFlags/flags/uniswapXExactOutput' import { useUniswapXExactOutputFlag } from 'featureFlags/flags/uniswapXExactOutput'
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react' import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
...@@ -267,6 +268,12 @@ export default function FeatureFlagModal() { ...@@ -267,6 +268,12 @@ export default function FeatureFlagModal() {
<X size={24} /> <X size={24} />
</CloseButton> </CloseButton>
</Header> </Header>
<FeatureFlagOption
variant={BaseVariant}
value={useFeesEnabledFlag()}
featureFlag={FeatureFlag.feesEnabled}
label="Enable Swap Fees"
/>
<FeatureFlagOption <FeatureFlagOption
variant={BaseVariant} variant={BaseVariant}
value={useFallbackProviderEnabledFlag()} value={useFallbackProviderEnabledFlag()}
......
...@@ -15,6 +15,7 @@ function useHasUpdatedTx() { ...@@ -15,6 +15,7 @@ function useHasUpdatedTx() {
return !!prevPendingActivityCount && pendingActivityCount < prevPendingActivityCount return !!prevPendingActivityCount && pendingActivityCount < prevPendingActivityCount
} }
// TODO(WEB-3004) - Add useCachedPortfolioBalanceUsd to simplify usage of useCachedPortfolioBalancesQuery
export function useCachedPortfolioBalancesQuery({ account }: { account?: string }) { export function useCachedPortfolioBalancesQuery({ account }: { account?: string }) {
return usePortfolioBalancesQuery({ return usePortfolioBalancesQuery({
skip: !account, skip: !account,
......
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { TraceEvent } from 'analytics' import { TraceEvent } from 'analytics'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import { AutoRow } from 'components/Row' import { AutoRow } from 'components/Row'
import { COMMON_BASES } from 'constants/routing' import { COMMON_BASES } from 'constants/routing'
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
...@@ -30,13 +32,19 @@ const BaseWrapper = styled.div<{ disable?: boolean }>` ...@@ -30,13 +32,19 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
background-color: ${({ theme, disable }) => disable && theme.surface3}; background-color: ${({ theme, disable }) => disable && theme.surface3};
` `
const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({ const formatAnalyticsEventProperties = (
currency: Currency,
searchQuery: string,
isAddressSearch: string | false,
portfolioBalanceUsd: number | undefined
) => ({
token_symbol: currency?.symbol, token_symbol: currency?.symbol,
token_chain_id: currency?.chainId, token_chain_id: currency?.chainId,
token_address: getTokenAddress(currency), token_address: getTokenAddress(currency),
is_suggested_token: true, is_suggested_token: true,
is_selected_from_list: false, is_selected_from_list: false,
is_imported_by_user: false, is_imported_by_user: false,
total_balances_usd: portfolioBalanceUsd,
...(isAddressSearch === false ...(isAddressSearch === false
? { search_token_symbol_input: searchQuery } ? { search_token_symbol_input: searchQuery }
: { search_token_address_input: isAddressSearch }), : { search_token_address_input: isAddressSearch }),
...@@ -54,8 +62,12 @@ export default function CommonBases({ ...@@ -54,8 +62,12 @@ export default function CommonBases({
onSelect: (currency: Currency) => void onSelect: (currency: Currency) => void
searchQuery: string searchQuery: string
isAddressSearch: string | false isAddressSearch: string | false
portfolioBalanceUsd?: number
}) { }) {
const bases = chainId !== undefined ? COMMON_BASES[chainId] ?? [] : [] const bases = chainId !== undefined ? COMMON_BASES[chainId] ?? [] : []
const { account } = useWeb3React()
const { data } = useCachedPortfolioBalancesQuery({ account })
const portfolioBalanceUsd = data?.portfolios?.[0].tokensTotalDenominatedValue?.value
return bases.length > 0 ? ( return bases.length > 0 ? (
<AutoRow gap="4px"> <AutoRow gap="4px">
...@@ -66,7 +78,7 @@ export default function CommonBases({ ...@@ -66,7 +78,7 @@ export default function CommonBases({
<TraceEvent <TraceEvent
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]} events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
name={InterfaceEventName.TOKEN_SELECTED} name={InterfaceEventName.TOKEN_SELECTED}
properties={formatAnalyticsEventProperties(currency, searchQuery, isAddressSearch)} properties={formatAnalyticsEventProperties(currency, searchQuery, isAddressSearch, portfolioBalanceUsd)}
element={InterfaceElementName.COMMON_BASES_CURRENCY_BUTTON} element={InterfaceElementName.COMMON_BASES_CURRENCY_BUTTON}
key={currencyId(currency)} key={currencyId(currency)}
> >
......
...@@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' ...@@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { TraceEvent } from 'analytics' import { TraceEvent } from 'analytics'
import Loader from 'components/Icons/LoadingSpinner' import Loader from 'components/Icons/LoadingSpinner'
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon' import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
import { checkWarning } from 'constants/tokenSafety' import { checkWarning } from 'constants/tokenSafety'
import { TokenBalances } from 'lib/hooks/useTokenList/sorting' import { TokenBalances } from 'lib/hooks/useTokenList/sorting'
...@@ -128,13 +129,15 @@ export function CurrencyRow({ ...@@ -128,13 +129,15 @@ export function CurrencyRow({
const warning = currency.isNative ? null : checkWarning(currency.address) const warning = currency.isNative ? null : checkWarning(currency.address)
const isBlockedToken = !!warning && !warning.canProceed const isBlockedToken = !!warning && !warning.canProceed
const blockedTokenOpacity = '0.6' const blockedTokenOpacity = '0.6'
const { data } = useCachedPortfolioBalancesQuery({ account })
const portfolioBalanceUsd = data?.portfolios?.[0].tokensTotalDenominatedValue?.value
// only show add or remove buttons if not on selected list // only show add or remove buttons if not on selected list
return ( return (
<TraceEvent <TraceEvent
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]} events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
name={InterfaceEventName.TOKEN_SELECTED} name={InterfaceEventName.TOKEN_SELECTED}
properties={{ is_imported_by_user: customAdded, ...eventProperties }} properties={{ is_imported_by_user: customAdded, ...eventProperties, total_balances_usd: portfolioBalanceUsd }}
element={InterfaceElementName.TOKEN_SELECTOR_ROW} element={InterfaceElementName.TOKEN_SELECTOR_ROW}
> >
<MenuItem <MenuItem
......
...@@ -31,7 +31,10 @@ const GasCostItem = ({ title, amount, itemValue }: GasCostItemProps) => { ...@@ -31,7 +31,10 @@ const GasCostItem = ({ title, amount, itemValue }: GasCostItemProps) => {
) )
} }
const GaslessSwapLabel = () => <UniswapXRouterLabel>$0</UniswapXRouterLabel> const GaslessSwapLabel = () => {
const { formatNumber } = useFormatter()
return <UniswapXRouterLabel>{formatNumber({ input: 0, type: NumberType.FiatGasPrice })}</UniswapXRouterLabel>
}
type GasBreakdownTooltipProps = { trade: InterfaceTrade; hideUniswapXDescription?: boolean } type GasBreakdownTooltipProps = { trade: InterfaceTrade; hideUniswapXDescription?: boolean }
......
...@@ -11,6 +11,8 @@ import { act, render, screen } from 'test-utils/render' ...@@ -11,6 +11,8 @@ import { act, render, screen } from 'test-utils/render'
import SwapDetailsDropdown from './SwapDetailsDropdown' import SwapDetailsDropdown from './SwapDetailsDropdown'
jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true }))
describe('SwapDetailsDropdown.tsx', () => { describe('SwapDetailsDropdown.tsx', () => {
it('renders a trade', () => { it('renders a trade', () => {
const { asFragment } = render( const { asFragment } = render(
......
...@@ -111,6 +111,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) { ...@@ -111,6 +111,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.SWAP_FEE} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
<Separator /> <Separator />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
......
...@@ -11,6 +11,8 @@ import { ...@@ -11,6 +11,8 @@ import {
} from 'test-utils/constants' } from 'test-utils/constants'
import { render } from 'test-utils/render' import { render } from 'test-utils/render'
jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true }))
// Forces tooltips to render in snapshots // Forces tooltips to render in snapshots
jest.mock('react-dom', () => { jest.mock('react-dom', () => {
const original = jest.requireActual('react-dom') const original = jest.requireActual('react-dom')
......
...@@ -6,11 +6,13 @@ import RouterLabel from 'components/RouterLabel' ...@@ -6,11 +6,13 @@ import RouterLabel from 'components/RouterLabel'
import Row, { RowBetween } from 'components/Row' import Row, { RowBetween } from 'components/Row'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { useFeesEnabled } from 'featureFlags/flags/useFees'
import useHoverProps from 'hooks/useHoverProps' import useHoverProps from 'hooks/useHoverProps'
import { useUSDPrice } from 'hooks/useUSDPrice'
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import React, { PropsWithChildren, useEffect, useState } from 'react' import React, { PropsWithChildren, useEffect, useState } from 'react'
import { animated, SpringValue } from 'react-spring' import { animated, SpringValue } from 'react-spring'
import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { InterfaceTrade, SubmittableTrade, TradeFillType } from 'state/routing/types'
import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils' import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils'
import { useUserSlippageTolerance } from 'state/user/hooks' import { useUserSlippageTolerance } from 'state/user/hooks'
import { SlippageTolerance } from 'state/user/types' import { SlippageTolerance } from 'state/user/types'
...@@ -31,6 +33,7 @@ export enum SwapLineItemType { ...@@ -31,6 +33,7 @@ export enum SwapLineItemType {
OUTPUT_TOKEN_FEE_ON_TRANSFER, OUTPUT_TOKEN_FEE_ON_TRANSFER,
PRICE_IMPACT, PRICE_IMPACT,
MAX_SLIPPAGE, MAX_SLIPPAGE,
SWAP_FEE,
MAXIMUM_INPUT, MAXIMUM_INPUT,
MINIMUM_OUTPUT, MINIMUM_OUTPUT,
ROUTING_INFO, ROUTING_INFO,
...@@ -74,6 +77,28 @@ function FOTTooltipContent() { ...@@ -74,6 +77,28 @@ function FOTTooltipContent() {
) )
} }
function SwapFeeTooltipContent({ hasFee }: { hasFee: boolean }) {
const message = hasFee ? (
<Trans>
Fee is applied on a few token pairs to ensure the best experience with Uniswap. It is paid in the output token and
has already been factored into the quote.
</Trans>
) : (
<Trans>
Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with
this swap.
</Trans>
)
return (
<>
{message}{' '}
<ExternalLink href="https://uniswap.org/docs/v2/protocol-overview/fees/">
<Trans>Learn more</Trans>
</ExternalLink>
</>
)
}
function Loading({ width = 50 }: { width?: number }) { function Loading({ width = 50 }: { width?: number }) {
return <LoadingRow data-testid="loading-row" height={15} width={width} /> return <LoadingRow data-testid="loading-row" height={15} width={width} />
} }
...@@ -89,6 +114,18 @@ function CurrencyAmountRow({ amount }: { amount: CurrencyAmount<Currency> }) { ...@@ -89,6 +114,18 @@ function CurrencyAmountRow({ amount }: { amount: CurrencyAmount<Currency> }) {
return <>{`${formattedAmount} ${amount.currency.symbol}`}</> return <>{`${formattedAmount} ${amount.currency.symbol}`}</>
} }
function FeeRow({ trade: { swapFee, outputAmount } }: { trade: SubmittableTrade }) {
const { formatNumber } = useFormatter()
const feeCurrencyAmount = CurrencyAmount.fromRawAmount(outputAmount.currency, swapFee?.amount ?? 0)
const { data: outputFeeFiatValue } = useUSDPrice(feeCurrencyAmount, feeCurrencyAmount?.currency)
// Fallback to displaying token amount if fiat value is not available
if (outputFeeFiatValue === undefined) return <CurrencyAmountRow amount={feeCurrencyAmount} />
return <>{formatNumber({ input: outputFeeFiatValue, type: NumberType.FiatGasPrice })}</>
}
type LineItemData = { type LineItemData = {
Label: React.FC Label: React.FC
Value: React.FC Value: React.FC
...@@ -101,6 +138,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { ...@@ -101,6 +138,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
const { trade, syncing, allowedSlippage, type } = props const { trade, syncing, allowedSlippage, type } = props
const { formatNumber, formatSlippage } = useFormatter() const { formatNumber, formatSlippage } = useFormatter()
const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto
const feesEnabled = useFeesEnabled()
const isUniswapX = isUniswapXTrade(trade) const isUniswapX = isUniswapXTrade(trade)
const isPreview = isPreviewTrade(trade) const isPreview = isPreviewTrade(trade)
...@@ -153,6 +191,19 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { ...@@ -153,6 +191,19 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
</Row> </Row>
), ),
} }
case SwapLineItemType.SWAP_FEE: {
if (!feesEnabled) return
if (isPreview) return { Label: () => <Trans>Fee</Trans>, Value: () => <Loading /> }
return {
Label: () => (
<>
<Trans>Fee</Trans> {trade.swapFee && `(${formatSlippage(trade.swapFee.percent)})`}
</>
),
TooltipBody: () => <SwapFeeTooltipContent hasFee={Boolean(trade.swapFee)} />,
Value: () => <FeeRow trade={trade} />,
}
}
case SwapLineItemType.MAXIMUM_INPUT: case SwapLineItemType.MAXIMUM_INPUT:
if (trade.tradeType === TradeType.EXACT_INPUT) return if (trade.tradeType === TradeType.EXACT_INPUT) return
return { return {
......
...@@ -3,6 +3,8 @@ import { render, screen, within } from 'test-utils/render' ...@@ -3,6 +3,8 @@ import { render, screen, within } from 'test-utils/render'
import SwapModalFooter from './SwapModalFooter' import SwapModalFooter from './SwapModalFooter'
jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true }))
describe('SwapModalFooter.tsx', () => { describe('SwapModalFooter.tsx', () => {
it('matches base snapshot, test trade exact input', () => { it('matches base snapshot, test trade exact input', () => {
const { asFragment } = render( const { asFragment } = render(
......
...@@ -119,6 +119,7 @@ export default function SwapModalFooter({ ...@@ -119,6 +119,7 @@ export default function SwapModalFooter({
<ExpandableLineItems {...lineItemProps} open={showMore} /> <ExpandableLineItems {...lineItemProps} open={showMore} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.SWAP_FEE} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
</DetailsContainer> </DetailsContainer>
{showAcceptChanges ? ( {showAcceptChanges ? (
......
...@@ -384,6 +384,29 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -384,6 +384,29 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div>
<div
class="c2 c3 c4"
>
<div
class="c9 c19 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c12"
>
<div>
<div
class="c9 c20 css-142zc9n"
>
0 DEF
</div>
</div>
</div>
</div>
</div>
<div> <div>
<div <div
class="c2 c3 c4" class="c2 c3 c4"
......
...@@ -228,7 +228,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` ...@@ -228,7 +228,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = `
ethereum.svg ethereum.svg
</svg> </svg>
</svg> </svg>
$0.00 $0
</div> </div>
</div> </div>
</div> </div>
...@@ -592,6 +592,214 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` ...@@ -592,6 +592,214 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c11 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c11::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c11.arrow-top {
bottom: -4px;
}
.c11.arrow-top::before {
border-top: none;
border-left: none;
}
.c11.arrow-bottom {
top: -4px;
}
.c11.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c11.arrow-left {
right: -4px;
}
.c11.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c11.arrow-right {
left: -4px;
}
.c11.arrow-right::before {
border-right: none;
border-top: none;
}
.c8 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
@supports (-webkit-background-clip:text) and (-webkit-text-fill-color:transparent) {
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
0 DEF
</div>
</div>
</div>
<div
class="c7"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c8"
>
<div
class="c9 css-obwv3p"
>
Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
<a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div>
</div>
<div
class="c11 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c7 { .c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
...@@ -1801,6 +2009,25 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1801,6 +2009,25 @@ exports[`SwapLineItem.tsx exact input 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 { .c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
...@@ -1816,13 +2043,13 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1816,13 +2043,13 @@ exports[`SwapLineItem.tsx exact input 1`] = `
height: inherit; height: inherit;
} }
.c10 { .c11 {
width: 8px; width: 8px;
height: 8px; height: 8px;
z-index: 9998; z-index: 9998;
} }
.c10::before { .c11::before {
position: absolute; position: absolute;
width: 8px; width: 8px;
height: 8px; height: 8px;
...@@ -1836,38 +2063,38 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1836,38 +2063,38 @@ exports[`SwapLineItem.tsx exact input 1`] = `
background: #FFFFFF; background: #FFFFFF;
} }
.c10.arrow-top { .c11.arrow-top {
bottom: -4px; bottom: -4px;
} }
.c10.arrow-top::before { .c11.arrow-top::before {
border-top: none; border-top: none;
border-left: none; border-left: none;
} }
.c10.arrow-bottom { .c11.arrow-bottom {
top: -4px; top: -4px;
} }
.c10.arrow-bottom::before { .c11.arrow-bottom::before {
border-bottom: none; border-bottom: none;
border-right: none; border-right: none;
} }
.c10.arrow-left { .c11.arrow-left {
right: -4px; right: -4px;
} }
.c10.arrow-left::before { .c11.arrow-left::before {
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.c10.arrow-right { .c11.arrow-right {
left: -4px; left: -4px;
} }
.c10.arrow-right::before { .c11.arrow-right::before {
border-right: none; border-right: none;
border-top: none; border-top: none;
} }
...@@ -1907,7 +2134,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1907,7 +2134,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
class="c3 c4 css-142zc9n" class="c3 c4 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Receive at least Fee
</div> </div>
<div <div
class="c5" class="c5"
...@@ -1916,7 +2143,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1916,7 +2143,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
<div <div
class="c3 c6 css-142zc9n" class="c3 c6 css-142zc9n"
> >
0.00000000000000098 DEF 0 DEF
</div> </div>
</div> </div>
</div> </div>
...@@ -1930,11 +2157,19 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1930,11 +2157,19 @@ exports[`SwapLineItem.tsx exact input 1`] = `
<div <div
class="c9 css-obwv3p" class="c9 css-obwv3p"
> >
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
<a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div> </div>
</div> </div>
<div <div
class="c10 arrow-" class="c11 arrow-"
style="position: absolute;" style="position: absolute;"
/> />
</div> </div>
...@@ -1946,7 +2181,184 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ...@@ -1946,7 +2181,184 @@ exports[`SwapLineItem.tsx exact input 1`] = `
min-width: 0; min-width: 0;
} }
.c23 { .c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c7 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c10 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c10::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c10.arrow-top {
bottom: -4px;
}
.c10.arrow-top::before {
border-top: none;
border-left: none;
}
.c10.arrow-bottom {
top: -4px;
}
.c10.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c10.arrow-left {
right: -4px;
}
.c10.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c10.arrow-right {
left: -4px;
}
.c10.arrow-right::before {
border-right: none;
border-top: none;
}
.c8 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Receive at least
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
0.00000000000000098 DEF
</div>
</div>
</div>
<div
class="c7"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c8"
>
<div
class="c9 css-obwv3p"
>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
</div>
</div>
<div
class="c10 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c23 {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
min-width: 0; min-width: 0;
...@@ -3264,6 +3676,25 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3264,6 +3676,25 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 { .c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
...@@ -3279,13 +3710,13 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3279,13 +3710,13 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
height: inherit; height: inherit;
} }
.c10 { .c11 {
width: 8px; width: 8px;
height: 8px; height: 8px;
z-index: 9998; z-index: 9998;
} }
.c10::before { .c11::before {
position: absolute; position: absolute;
width: 8px; width: 8px;
height: 8px; height: 8px;
...@@ -3299,38 +3730,38 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3299,38 +3730,38 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
background: #FFFFFF; background: #FFFFFF;
} }
.c10.arrow-top { .c11.arrow-top {
bottom: -4px; bottom: -4px;
} }
.c10.arrow-top::before { .c11.arrow-top::before {
border-top: none; border-top: none;
border-left: none; border-left: none;
} }
.c10.arrow-bottom { .c11.arrow-bottom {
top: -4px; top: -4px;
} }
.c10.arrow-bottom::before { .c11.arrow-bottom::before {
border-bottom: none; border-bottom: none;
border-right: none; border-right: none;
} }
.c10.arrow-left { .c11.arrow-left {
right: -4px; right: -4px;
} }
.c10.arrow-left::before { .c11.arrow-left::before {
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.c10.arrow-right { .c11.arrow-right {
left: -4px; left: -4px;
} }
.c10.arrow-right::before { .c11.arrow-right::before {
border-right: none; border-right: none;
border-top: none; border-top: none;
} }
...@@ -3370,7 +3801,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3370,7 +3801,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
class="c3 c4 css-142zc9n" class="c3 c4 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Receive at least Fee
</div> </div>
<div <div
class="c5" class="c5"
...@@ -3379,7 +3810,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3379,7 +3810,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
<div <div
class="c3 c6 css-142zc9n" class="c3 c6 css-142zc9n"
> >
0.00000000000000098 DEF 0 DEF
</div> </div>
</div> </div>
</div> </div>
...@@ -3393,11 +3824,19 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3393,11 +3824,19 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
<div <div
class="c9 css-obwv3p" class="c9 css-obwv3p"
> >
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
<a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div> </div>
</div> </div>
<div <div
class="c10 arrow-" class="c11 arrow-"
style="position: absolute;" style="position: absolute;"
/> />
</div> </div>
...@@ -3409,13 +3848,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3409,13 +3848,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
min-width: 0; min-width: 0;
} }
.c23 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: 100%;
}
.c1 { .c1 {
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
...@@ -3433,24 +3865,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3433,24 +3865,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
justify-content: flex-start; justify-content: flex-start;
} }
.c24 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 1px;
}
.c2 { .c2 {
-webkit-box-pack: justify; -webkit-box-pack: justify;
-webkit-justify-content: space-between; -webkit-justify-content: space-between;
...@@ -3458,8 +3872,210 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ...@@ -3458,8 +3872,210 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
justify-content: space-between; justify-content: space-between;
} }
.c25 { .c3 {
-webkit-flex-wrap: wrap; color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c7 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c10 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c10::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c10.arrow-top {
bottom: -4px;
}
.c10.arrow-top::before {
border-top: none;
border-left: none;
}
.c10.arrow-bottom {
top: -4px;
}
.c10.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c10.arrow-left {
right: -4px;
}
.c10.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c10.arrow-right {
left: -4px;
}
.c10.arrow-right::before {
border-right: none;
border-top: none;
}
.c8 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Receive at least
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
0.00000000000000098 DEF
</div>
</div>
</div>
<div
class="c7"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c8"
>
<div
class="c9 css-obwv3p"
>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
</div>
</div>
<div
class="c10 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c23 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: 100%;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c24 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 1px;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c25 {
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap; -ms-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
margin: -1px; margin: -1px;
...@@ -4727,6 +5343,25 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4727,6 +5343,25 @@ exports[`SwapLineItem.tsx exact output 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 { .c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
...@@ -4742,13 +5377,13 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4742,13 +5377,13 @@ exports[`SwapLineItem.tsx exact output 1`] = `
height: inherit; height: inherit;
} }
.c10 { .c11 {
width: 8px; width: 8px;
height: 8px; height: 8px;
z-index: 9998; z-index: 9998;
} }
.c10::before { .c11::before {
position: absolute; position: absolute;
width: 8px; width: 8px;
height: 8px; height: 8px;
...@@ -4762,38 +5397,38 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4762,38 +5397,38 @@ exports[`SwapLineItem.tsx exact output 1`] = `
background: #FFFFFF; background: #FFFFFF;
} }
.c10.arrow-top { .c11.arrow-top {
bottom: -4px; bottom: -4px;
} }
.c10.arrow-top::before { .c11.arrow-top::before {
border-top: none; border-top: none;
border-left: none; border-left: none;
} }
.c10.arrow-bottom { .c11.arrow-bottom {
top: -4px; top: -4px;
} }
.c10.arrow-bottom::before { .c11.arrow-bottom::before {
border-bottom: none; border-bottom: none;
border-right: none; border-right: none;
} }
.c10.arrow-left { .c11.arrow-left {
right: -4px; right: -4px;
} }
.c10.arrow-left::before { .c11.arrow-left::before {
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.c10.arrow-right { .c11.arrow-right {
left: -4px; left: -4px;
} }
.c10.arrow-right::before { .c11.arrow-right::before {
border-right: none; border-right: none;
border-top: none; border-top: none;
} }
...@@ -4833,7 +5468,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4833,7 +5468,7 @@ exports[`SwapLineItem.tsx exact output 1`] = `
class="c3 c4 css-142zc9n" class="c3 c4 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Pay at most Fee
</div> </div>
<div <div
class="c5" class="c5"
...@@ -4842,7 +5477,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4842,7 +5477,7 @@ exports[`SwapLineItem.tsx exact output 1`] = `
<div <div
class="c3 c6 css-142zc9n" class="c3 c6 css-142zc9n"
> >
0.00000000000000102 ABC 0 GHI
</div> </div>
</div> </div>
</div> </div>
...@@ -4856,11 +5491,19 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4856,11 +5491,19 @@ exports[`SwapLineItem.tsx exact output 1`] = `
<div <div
class="c9 css-obwv3p" class="c9 css-obwv3p"
> >
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
<a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div> </div>
</div> </div>
<div <div
class="c10 arrow-" class="c11 arrow-"
style="position: absolute;" style="position: absolute;"
/> />
</div> </div>
...@@ -4872,7 +5515,184 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ...@@ -4872,7 +5515,184 @@ exports[`SwapLineItem.tsx exact output 1`] = `
min-width: 0; min-width: 0;
} }
.c23 { .c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c7 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c10 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c10::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c10.arrow-top {
bottom: -4px;
}
.c10.arrow-top::before {
border-top: none;
border-left: none;
}
.c10.arrow-bottom {
top: -4px;
}
.c10.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c10.arrow-left {
right: -4px;
}
.c10.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c10.arrow-right {
left: -4px;
}
.c10.arrow-right::before {
border-right: none;
border-top: none;
}
.c8 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Pay at most
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
0.00000000000000102 ABC
</div>
</div>
</div>
<div
class="c7"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c8"
>
<div
class="c9 css-obwv3p"
>
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert.
</div>
</div>
<div
class="c10 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c23 {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
min-width: 0; min-width: 0;
...@@ -6398,6 +7218,25 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6398,6 +7218,25 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 { .c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
...@@ -6413,13 +7252,13 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6413,13 +7252,13 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
height: inherit; height: inherit;
} }
.c10 { .c11 {
width: 8px; width: 8px;
height: 8px; height: 8px;
z-index: 9998; z-index: 9998;
} }
.c10::before { .c11::before {
position: absolute; position: absolute;
width: 8px; width: 8px;
height: 8px; height: 8px;
...@@ -6433,38 +7272,38 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6433,38 +7272,38 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
background: #FFFFFF; background: #FFFFFF;
} }
.c10.arrow-top { .c11.arrow-top {
bottom: -4px; bottom: -4px;
} }
.c10.arrow-top::before { .c11.arrow-top::before {
border-top: none; border-top: none;
border-left: none; border-left: none;
} }
.c10.arrow-bottom { .c11.arrow-bottom {
top: -4px; top: -4px;
} }
.c10.arrow-bottom::before { .c11.arrow-bottom::before {
border-bottom: none; border-bottom: none;
border-right: none; border-right: none;
} }
.c10.arrow-left { .c11.arrow-left {
right: -4px; right: -4px;
} }
.c10.arrow-left::before { .c11.arrow-left::before {
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.c10.arrow-right { .c11.arrow-right {
left: -4px; left: -4px;
} }
.c10.arrow-right::before { .c11.arrow-right::before {
border-right: none; border-right: none;
border-top: none; border-top: none;
} }
...@@ -6504,7 +7343,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6504,7 +7343,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
class="c3 c4 css-142zc9n" class="c3 c4 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Receive at least Fee
</div> </div>
<div <div
class="c5" class="c5"
...@@ -6513,7 +7352,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6513,7 +7352,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
<div <div
class="c3 c6 css-142zc9n" class="c3 c6 css-142zc9n"
> >
0.000000000000000952 DEF 0 DEF
</div> </div>
</div> </div>
</div> </div>
...@@ -6527,11 +7366,19 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6527,11 +7366,19 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
<div <div
class="c9 css-obwv3p" class="c9 css-obwv3p"
> >
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
<a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div> </div>
</div> </div>
<div <div
class="c10 arrow-" class="c11 arrow-"
style="position: absolute;" style="position: absolute;"
/> />
</div> </div>
...@@ -6543,13 +7390,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6543,13 +7390,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
min-width: 0; min-width: 0;
} }
.c23 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: 100%;
}
.c1 { .c1 {
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
...@@ -6567,24 +7407,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6567,24 +7407,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
justify-content: flex-start; justify-content: flex-start;
} }
.c24 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 1px;
}
.c2 { .c2 {
-webkit-box-pack: justify; -webkit-box-pack: justify;
-webkit-justify-content: space-between; -webkit-justify-content: space-between;
...@@ -6592,8 +7414,210 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ...@@ -6592,8 +7414,210 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
justify-content: space-between; justify-content: space-between;
} }
.c25 { .c3 {
-webkit-flex-wrap: wrap; color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c7 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c10 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c10::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c10.arrow-top {
bottom: -4px;
}
.c10.arrow-top::before {
border-top: none;
border-left: none;
}
.c10.arrow-bottom {
top: -4px;
}
.c10.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c10.arrow-left {
right: -4px;
}
.c10.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c10.arrow-right {
left: -4px;
}
.c10.arrow-right::before {
border-right: none;
border-top: none;
}
.c8 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Receive at least
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
0.000000000000000952 DEF
</div>
</div>
</div>
<div
class="c7"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c8"
>
<div
class="c9 css-obwv3p"
>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
</div>
</div>
<div
class="c10 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c23 {
box-sizing: border-box;
margin: 0;
min-width: 0;
width: 100%;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c24 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 1px;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c25 {
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap; -ms-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
margin: -1px; margin: -1px;
...@@ -7827,7 +8851,268 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7827,7 +8851,268 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
background-color: #22222212; background-color: #22222212;
} }
.c10 { .c10 {
z-index: 1070;
pointer-events: none;
visibility: hidden;
opacity: 0;
-webkit-transition: visibility 150ms linear,opacity 150ms linear;
transition: visibility 150ms linear,opacity 150ms linear;
color: #7D7D7D;
}
.c5 {
display: inline-block;
height: inherit;
}
.c15 {
width: 8px;
height: 8px;
z-index: 9998;
}
.c15::before {
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
border: 1px solid #22222212;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
background: #FFFFFF;
}
.c15.arrow-top {
bottom: -4px;
}
.c15.arrow-top::before {
border-top: none;
border-left: none;
}
.c15.arrow-bottom {
top: -4px;
}
.c15.arrow-bottom::before {
border-bottom: none;
border-right: none;
}
.c15.arrow-left {
right: -4px;
}
.c15.arrow-left::before {
border-bottom: none;
border-left: none;
}
.c15.arrow-right {
left: -4px;
}
.c15.arrow-right::before {
border-right: none;
border-top: none;
}
.c11 {
max-width: 256px;
width: calc(100vw - 16px);
cursor: default;
padding: 12px;
pointer-events: auto;
color: #222222;
font-weight: 485;
font-size: 12px;
line-height: 16px;
word-break: break-word;
background: #FFFFFF;
border-radius: 12px;
border: 1px solid #22222212;
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
}
.c12 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
.c9 {
background: #22222212;
border-radius: 8px;
color: #7D7D7D;
height: 20px;
padding: 0 6px;
}
.c9::after {
content: 'Auto';
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Max. slippage
</div>
<div
class="c5"
>
<div>
<div
class="c3 c6 css-142zc9n"
>
<div
class="c0 c7"
>
<div
class="c8 c9 css-1lgneq0"
/>
2%
</div>
</div>
</div>
</div>
<div
class="c10"
style="position: fixed; left: 0px; top: 0px;"
>
<div
class="c11"
>
<div
class="c8 css-obwv3p"
>
<div
class="c12"
>
<div
class="c0 c1 c2"
>
<div
class="c3 css-obwv3p"
>
Receive at least
</div>
<div
class="c3 css-obwv3p"
>
0.000000000000000952 DEF
</div>
</div>
<div
class="c13"
/>
<div>
If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
<a
class="c14"
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div>
</div>
</div>
</div>
<div
class="c15 arrow-"
style="position: absolute;"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c9 {
color: #7D7D7D;
}
.c10 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FC72FF;
stroke: #FC72FF;
font-weight: 500;
}
.c10:hover {
opacity: 0.6;
}
.c10:active {
opacity: 0.4;
}
.c7 {
z-index: 1070; z-index: 1070;
pointer-events: none; pointer-events: none;
visibility: hidden; visibility: hidden;
...@@ -7842,13 +9127,13 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7842,13 +9127,13 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
height: inherit; height: inherit;
} }
.c15 { .c11 {
width: 8px; width: 8px;
height: 8px; height: 8px;
z-index: 9998; z-index: 9998;
} }
.c15::before { .c11::before {
position: absolute; position: absolute;
width: 8px; width: 8px;
height: 8px; height: 8px;
...@@ -7862,43 +9147,43 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7862,43 +9147,43 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
background: #FFFFFF; background: #FFFFFF;
} }
.c15.arrow-top { .c11.arrow-top {
bottom: -4px; bottom: -4px;
} }
.c15.arrow-top::before { .c11.arrow-top::before {
border-top: none; border-top: none;
border-left: none; border-left: none;
} }
.c15.arrow-bottom { .c11.arrow-bottom {
top: -4px; top: -4px;
} }
.c15.arrow-bottom::before { .c11.arrow-bottom::before {
border-bottom: none; border-bottom: none;
border-right: none; border-right: none;
} }
.c15.arrow-left { .c11.arrow-left {
right: -4px; right: -4px;
} }
.c15.arrow-left::before { .c11.arrow-left::before {
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.c15.arrow-right { .c11.arrow-right {
left: -4px; left: -4px;
} }
.c15.arrow-right::before { .c11.arrow-right::before {
border-right: none; border-right: none;
border-top: none; border-top: none;
} }
.c11 { .c8 {
max-width: 256px; max-width: 256px;
width: calc(100vw - 16px); width: calc(100vw - 16px);
cursor: default; cursor: default;
...@@ -7915,21 +9200,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7915,21 +9200,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1);
} }
.c12 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c6 { .c6 {
text-align: right; text-align: right;
overflow-wrap: break-word; overflow-wrap: break-word;
...@@ -7940,18 +9210,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7940,18 +9210,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
color: #7D7D7D; color: #7D7D7D;
} }
.c9 {
background: #22222212;
border-radius: 8px;
color: #7D7D7D;
height: 20px;
padding: 0 6px;
}
.c9::after {
content: 'Auto';
}
<div> <div>
<div <div
class="c0 c1 c2" class="c0 c1 c2"
...@@ -7960,7 +9218,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7960,7 +9218,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
class="c3 c4 css-142zc9n" class="c3 c4 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Max. slippage Fee
</div> </div>
<div <div
class="c5" class="c5"
...@@ -7969,63 +9227,33 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ...@@ -7969,63 +9227,33 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
<div <div
class="c3 c6 css-142zc9n" class="c3 c6 css-142zc9n"
> >
<div 0 DEF
class="c0 c7"
>
<div
class="c8 c9 css-1lgneq0"
/>
2%
</div>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="c10" class="c7"
style="position: fixed; left: 0px; top: 0px;" style="position: fixed; left: 0px; top: 0px;"
> >
<div <div
class="c11" class="c8"
> >
<div <div
class="c8 css-obwv3p" class="c9 css-obwv3p"
> >
<div Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap.
class="c12" <a
class="c10"
href="https://uniswap.org/docs/v2/protocol-overview/fees/"
rel="noopener noreferrer"
target="_blank"
> >
<div Learn more
class="c0 c1 c2" </a>
>
<div
class="c3 css-obwv3p"
>
Receive at least
</div>
<div
class="c3 css-obwv3p"
>
0.000000000000000952 DEF
</div>
</div>
<div
class="c13"
/>
<div>
If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive.
<a
class="c14"
href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div>
</div>
</div> </div>
</div> </div>
<div <div
class="c15 arrow-" class="c11 arrow-"
style="position: absolute;" style="position: absolute;"
/> />
</div> </div>
...@@ -9509,6 +10737,85 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` ...@@ -9509,6 +10737,85 @@ exports[`SwapLineItem.tsx preview exact in 1`] = `
color: #222222; color: #222222;
} }
.c6 {
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left, #FFFFFF 25%, #22222212 50%, #FFFFFF 75% );
background-size: 400%;
will-change: background-position;
border-radius: 12px;
height: 15px;
width: 50px;
}
.c5 {
text-align: right;
overflow-wrap: break-word;
}
.c4 {
cursor: auto;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c3 c5 css-142zc9n"
>
<div
class="c6"
data-testid="loading-row"
height="15"
width="50"
/>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c9 { .c9 {
color: #7D7D7D; color: #7D7D7D;
} }
...@@ -9980,6 +11287,76 @@ exports[`SwapLineItem.tsx syncing 1`] = ` ...@@ -9980,6 +11287,76 @@ exports[`SwapLineItem.tsx syncing 1`] = `
color: #222222; color: #222222;
} }
.c5 {
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left, #FFFFFF 25%, #22222212 50%, #FFFFFF 75% );
background-size: 400%;
will-change: background-position;
border-radius: 12px;
height: 15px;
width: 50px;
}
.c4 {
cursor: help;
color: #7D7D7D;
}
<div>
<div
class="c0 c1 c2"
>
<div
class="c3 c4 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c5"
data-testid="loading-row"
height="15"
width="50"
/>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c2 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.c3 {
color: #222222;
}
.c5 { .c5 {
-webkit-animation: fAQEyV 1.5s infinite; -webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite; animation: fAQEyV 1.5s infinite;
......
...@@ -354,6 +354,29 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = ...@@ -354,6 +354,29 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
</div> </div>
</div> </div>
</div> </div>
<div>
<div
class="c2 c3 c4"
>
<div
class="c5 c6 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c7"
>
<div>
<div
class="c5 c8 css-142zc9n"
>
0 DEF
</div>
</div>
</div>
</div>
</div>
<div> <div>
<div <div
class="c2 c3 c4" class="c2 c3 c4"
...@@ -938,6 +961,28 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission ...@@ -938,6 +961,28 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
</div> </div>
</div> </div>
</div> </div>
<div>
<div
class="c2 c3 c4"
>
<div
class="c5 c6 css-142zc9n"
data-testid="swap-li-label"
>
Fee
</div>
<div
class="c5 c7 css-142zc9n"
>
<div
class="c11"
data-testid="loading-row"
height="15"
width="50"
/>
</div>
</div>
</div>
<div> <div>
<div <div
class="c2 c3 c4" class="c2 c3 c4"
......
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useFeesEnabledFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.feesEnabled)
}
export function useFeesEnabled(): boolean {
return useFeesEnabledFlag() === BaseVariant.Enabled
}
...@@ -22,6 +22,7 @@ export enum FeatureFlag { ...@@ -22,6 +22,7 @@ export enum FeatureFlag {
uniswapXDefaultEnabled = 'uniswapx_default_enabled', uniswapXDefaultEnabled = 'uniswapx_default_enabled',
quickRouteMainnet = 'enable_quick_route_mainnet', quickRouteMainnet = 'enable_quick_route_mainnet',
progressIndicatorV2 = 'progress_indicator_v2', progressIndicatorV2 = 'progress_indicator_v2',
feesEnabled = 'fees_enabled',
} }
interface FeatureFlagsContextType { interface FeatureFlagsContextType {
......
import { Percent, TradeType } from '@uniswap/sdk-core' import { Percent, TradeType } from '@uniswap/sdk-core'
import { FlatFeeOptions } from '@uniswap/universal-router-sdk'
import { FeeOptions } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { BigNumber } from 'ethers/lib/ethers'
import { PermitSignature } from 'hooks/usePermitAllowance' import { PermitSignature } from 'hooks/usePermitAllowance'
import { useCallback } from 'react' import { useCallback } from 'react'
import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { InterfaceTrade, TradeFillType } from 'state/routing/types'
...@@ -20,11 +23,24 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter' ...@@ -20,11 +23,24 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter'
export type SwapResult = Awaited<ReturnType<ReturnType<typeof useSwapCallback>>> export type SwapResult = Awaited<ReturnType<ReturnType<typeof useSwapCallback>>>
type UniversalRouterFeeField = { feeOptions: FeeOptions } | { flatFeeOptions: FlatFeeOptions }
function getUniversalRouterFeeFields(trade?: InterfaceTrade): UniversalRouterFeeField | undefined {
if (!isClassicTrade(trade)) return undefined
if (!trade.swapFee) return undefined
if (trade.tradeType === TradeType.EXACT_INPUT) {
return { feeOptions: { fee: trade.swapFee.percent, recipient: trade.swapFee.recipient } }
} else {
return { flatFeeOptions: { amount: BigNumber.from(trade.swapFee.amount), recipient: trade.swapFee.recipient } }
}
}
// Returns a function that will execute a swap, if the parameters are all valid // Returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade // and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback( export function useSwapCallback(
trade: InterfaceTrade | undefined, // trade to execute, required trade: InterfaceTrade | undefined, // trade to execute, required
fiatValues: { amountIn?: number; amountOut?: number }, // usd values for amount in and out, logged for analytics fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }, // usd values for amount in and out, and the fee value, logged for analytics
allowedSlippage: Percent, // in bips allowedSlippage: Percent, // in bips
permitSignature: PermitSignature | undefined permitSignature: PermitSignature | undefined
) { ) {
...@@ -47,6 +63,7 @@ export function useSwapCallback( ...@@ -47,6 +63,7 @@ export function useSwapCallback(
slippageTolerance: allowedSlippage, slippageTolerance: allowedSlippage,
deadline, deadline,
permit: permitSignature, permit: permitSignature,
...getUniversalRouterFeeFields(trade),
} }
) )
......
...@@ -5,6 +5,7 @@ import { Percent } from '@uniswap/sdk-core' ...@@ -5,6 +5,7 @@ import { Percent } from '@uniswap/sdk-core'
import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk' import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent, useTrace } from 'analytics' import { sendAnalyticsEvent, useTrace } from 'analytics'
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
import { useCallback } from 'react' import { useCallback } from 'react'
import { DutchOrderTrade, TradeFillType } from 'state/routing/types' import { DutchOrderTrade, TradeFillType } from 'state/routing/types'
...@@ -50,12 +51,15 @@ export function useUniswapXSwapCallback({ ...@@ -50,12 +51,15 @@ export function useUniswapXSwapCallback({
fiatValues, fiatValues,
}: { }: {
trade?: DutchOrderTrade trade?: DutchOrderTrade
fiatValues: { amountIn?: number; amountOut?: number } fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }
allowedSlippage: Percent allowedSlippage: Percent
}) { }) {
const { account, provider } = useWeb3React() const { account, provider } = useWeb3React()
const analyticsContext = useTrace() const analyticsContext = useTrace()
const { data } = useCachedPortfolioBalancesQuery({ account })
const portfolioBalanceUsd = data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value
return useCallback( return useCallback(
async () => async () =>
trace('swapx.send', async ({ setTraceData, setTraceStatus }) => { trace('swapx.send', async ({ setTraceData, setTraceStatus }) => {
...@@ -82,7 +86,7 @@ export function useUniswapXSwapCallback({ ...@@ -82,7 +86,7 @@ export function useUniswapXSwapCallback({
.decayEndTime(endTime) .decayEndTime(endTime)
.deadline(deadline) .deadline(deadline)
.swapper(account) .swapper(account)
.nonFeeRecipient(account) .nonFeeRecipient(account, trade.swapFee?.recipient)
// if fetching the nonce fails for any reason, default to existing nonce from the Swap quote. // if fetching the nonce fails for any reason, default to existing nonce from the Swap quote.
.nonce(updatedNonce ?? trade.order.info.nonce) .nonce(updatedNonce ?? trade.order.info.nonce)
.build() .build()
...@@ -115,6 +119,7 @@ export function useUniswapXSwapCallback({ ...@@ -115,6 +119,7 @@ export function useUniswapXSwapCallback({
allowedSlippage, allowedSlippage,
fiatValues, fiatValues,
timeToSignSinceRequestMs: Date.now() - beforeSign, timeToSignSinceRequestMs: Date.now() - beforeSign,
portfolioBalanceUsd,
}), }),
...analyticsContext, ...analyticsContext,
}) })
...@@ -139,6 +144,7 @@ export function useUniswapXSwapCallback({ ...@@ -139,6 +144,7 @@ export function useUniswapXSwapCallback({
trade, trade,
allowedSlippage, allowedSlippage,
fiatValues, fiatValues,
portfolioBalanceUsd,
}), }),
...analyticsContext, ...analyticsContext,
errorCode: body.errorCode, errorCode: body.errorCode,
...@@ -154,6 +160,6 @@ export function useUniswapXSwapCallback({ ...@@ -154,6 +160,6 @@ export function useUniswapXSwapCallback({
response: { orderHash: body.hash, deadline: updatedOrder.info.deadline }, response: { orderHash: body.hash, deadline: updatedOrder.info.deadline },
} }
}), }),
[account, provider, trade, allowedSlippage, fiatValues, analyticsContext] [account, provider, trade, allowedSlippage, fiatValues, analyticsContext, portfolioBalanceUsd]
) )
} }
...@@ -2,10 +2,11 @@ import { BigNumber } from '@ethersproject/bignumber' ...@@ -2,10 +2,11 @@ import { BigNumber } from '@ethersproject/bignumber'
import { t } from '@lingui/macro' import { t } from '@lingui/macro'
import { SwapEventName } from '@uniswap/analytics-events' import { SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { FlatFeeOptions, SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { FeeOptions, toHex } from '@uniswap/v3-sdk' import { FeeOptions, toHex } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent, useTrace } from 'analytics' import { sendAnalyticsEvent, useTrace } from 'analytics'
import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import useBlockNumber from 'lib/hooks/useBlockNumber' import useBlockNumber from 'lib/hooks/useBlockNumber'
import { formatCommonPropertiesForTrade, formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { formatCommonPropertiesForTrade, formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
import { useCallback } from 'react' import { useCallback } from 'react'
...@@ -43,17 +44,20 @@ interface SwapOptions { ...@@ -43,17 +44,20 @@ interface SwapOptions {
deadline?: BigNumber deadline?: BigNumber
permit?: PermitSignature permit?: PermitSignature
feeOptions?: FeeOptions feeOptions?: FeeOptions
flatFeeOptions?: FlatFeeOptions
} }
export function useUniversalRouterSwapCallback( export function useUniversalRouterSwapCallback(
trade: ClassicTrade | undefined, trade: ClassicTrade | undefined,
fiatValues: { amountIn?: number; amountOut?: number }, fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number },
options: SwapOptions options: SwapOptions
) { ) {
const { account, chainId, provider } = useWeb3React() const { account, chainId, provider } = useWeb3React()
const analyticsContext = useTrace() const analyticsContext = useTrace()
const blockNumber = useBlockNumber() const blockNumber = useBlockNumber()
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto' const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
const { data } = useCachedPortfolioBalancesQuery({ account })
const portfolioBalanceUsd = data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value
return useCallback(async () => { return useCallback(async () => {
return trace('swap.send', async ({ setTraceData, setTraceStatus, setTraceError }) => { return trace('swap.send', async ({ setTraceData, setTraceStatus, setTraceError }) => {
...@@ -76,6 +80,7 @@ export function useUniversalRouterSwapCallback( ...@@ -76,6 +80,7 @@ export function useUniversalRouterSwapCallback(
deadlineOrPreviousBlockhash: options.deadline?.toString(), deadlineOrPreviousBlockhash: options.deadline?.toString(),
inputTokenPermit: options.permit, inputTokenPermit: options.permit,
fee: options.feeOptions, fee: options.feeOptions,
flatFee: options.flatFeeOptions,
}) })
const tx = { const tx = {
...@@ -117,6 +122,7 @@ export function useUniversalRouterSwapCallback( ...@@ -117,6 +122,7 @@ export function useUniversalRouterSwapCallback(
allowedSlippage: options.slippageTolerance, allowedSlippage: options.slippageTolerance,
fiatValues, fiatValues,
txHash: response.hash, txHash: response.hash,
portfolioBalanceUsd,
}), }),
...analyticsContext, ...analyticsContext,
}) })
...@@ -154,16 +160,14 @@ export function useUniversalRouterSwapCallback( ...@@ -154,16 +160,14 @@ export function useUniversalRouterSwapCallback(
}) })
}, [ }, [
account, account,
analyticsContext,
blockNumber,
chainId, chainId,
fiatValues,
options.deadline,
options.feeOptions,
options.permit,
options.slippageTolerance,
provider, provider,
trade, trade,
options,
analyticsContext,
blockNumber,
isAutoSlippage, isAutoSlippage,
fiatValues,
portfolioBalanceUsd,
]) ])
} }
...@@ -4,6 +4,7 @@ import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' ...@@ -4,6 +4,7 @@ import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput'
import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput' import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput'
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
import { useFeesEnabled } from 'featureFlags/flags/useFees'
import { useMemo } from 'react' import { useMemo } from 'react'
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
import { currencyAddressForSwapQuote } from 'state/routing/utils' import { currencyAddressForSwapQuote } from 'state/routing/utils'
...@@ -40,6 +41,10 @@ export function useRoutingAPIArguments({ ...@@ -40,6 +41,10 @@ export function useRoutingAPIArguments({
const uniswapXExactOutputEnabled = useUniswapXExactOutputEnabled() const uniswapXExactOutputEnabled = useUniswapXExactOutputEnabled()
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
const feesEnabled = useFeesEnabled()
// Don't enable fee logic if this is a quote for pricing
const sendPortionEnabled = routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? false : feesEnabled
return useMemo( return useMemo(
() => () =>
!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut) || tokenIn.wrapped.equals(tokenOut.wrapped) !tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut) || tokenIn.wrapped.equals(tokenOut.wrapped)
...@@ -64,6 +69,7 @@ export function useRoutingAPIArguments({ ...@@ -64,6 +69,7 @@ export function useRoutingAPIArguments({
uniswapXEthOutputEnabled, uniswapXEthOutputEnabled,
uniswapXExactOutputEnabled, uniswapXExactOutputEnabled,
isUniswapXDefaultEnabled, isUniswapXDefaultEnabled,
sendPortionEnabled,
inputTax, inputTax,
outputTax, outputTax,
}, },
...@@ -80,6 +86,7 @@ export function useRoutingAPIArguments({ ...@@ -80,6 +86,7 @@ export function useRoutingAPIArguments({
userOptedOutOfUniswapX, userOptedOutOfUniswapX,
uniswapXEthOutputEnabled, uniswapXEthOutputEnabled,
isUniswapXDefaultEnabled, isUniswapXDefaultEnabled,
sendPortionEnabled,
inputTax, inputTax,
outputTax, outputTax,
] ]
......
import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core'
import { NATIVE_CHAIN_ID } from 'constants/tokens' import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { InterfaceTrade, QuoteMethod } from 'state/routing/types' import { InterfaceTrade, QuoteMethod, SubmittableTrade } from 'state/routing/types'
import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routing/utils' import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routing/utils'
import { computeRealizedPriceImpact } from 'utils/prices' import { computeRealizedPriceImpact } from 'utils/prices'
...@@ -35,7 +35,11 @@ function getEstimatedNetworkFee(trade: InterfaceTrade) { ...@@ -35,7 +35,11 @@ function getEstimatedNetworkFee(trade: InterfaceTrade) {
return undefined return undefined
} }
export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSlippage: Percent) { export function formatCommonPropertiesForTrade(
trade: InterfaceTrade,
allowedSlippage: Percent,
outputFeeFiatValue?: number
) {
return { return {
routing: trade.fillType, routing: trade.fillType,
type: trade.tradeType, type: trade.tradeType,
...@@ -47,7 +51,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli ...@@ -47,7 +51,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli
token_in_symbol: trade.inputAmount.currency.symbol, token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals), token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals), token_out_amount: formatToDecimal(trade.postTaxOutputAmount, trade.outputAmount.currency.decimals),
price_impact_basis_points: isClassicTrade(trade) price_impact_basis_points: isClassicTrade(trade)
? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade))
: undefined, : undefined,
...@@ -59,6 +63,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli ...@@ -59,6 +63,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli
minimum_output_after_slippage: trade.minimumAmountOut(allowedSlippage).toSignificant(6), minimum_output_after_slippage: trade.minimumAmountOut(allowedSlippage).toSignificant(6),
allowed_slippage: formatPercentNumber(allowedSlippage), allowed_slippage: formatPercentNumber(allowedSlippage),
method: getQuoteMethod(trade), method: getQuoteMethod(trade),
fee_usd: outputFeeFiatValue,
} }
} }
...@@ -68,19 +73,22 @@ export const formatSwapSignedAnalyticsEventProperties = ({ ...@@ -68,19 +73,22 @@ export const formatSwapSignedAnalyticsEventProperties = ({
fiatValues, fiatValues,
txHash, txHash,
timeToSignSinceRequestMs, timeToSignSinceRequestMs,
portfolioBalanceUsd,
}: { }: {
trade: InterfaceTrade trade: SubmittableTrade
allowedSlippage: Percent allowedSlippage: Percent
fiatValues: { amountIn?: number; amountOut?: number } fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }
txHash?: string txHash?: string
timeToSignSinceRequestMs?: number timeToSignSinceRequestMs?: number
portfolioBalanceUsd?: number
}) => ({ }) => ({
total_balances_usd: portfolioBalanceUsd,
transaction_hash: txHash, transaction_hash: txHash,
token_in_amount_usd: fiatValues.amountIn, token_in_amount_usd: fiatValues.amountIn,
token_out_amount_usd: fiatValues.amountOut, token_out_amount_usd: fiatValues.amountOut,
// measures the amount of time the user took to sign the permit message or swap tx in their wallet // measures the amount of time the user took to sign the permit message or swap tx in their wallet
time_to_sign_since_request_ms: timeToSignSinceRequestMs, time_to_sign_since_request_ms: timeToSignSinceRequestMs,
...formatCommonPropertiesForTrade(trade, allowedSlippage), ...formatCommonPropertiesForTrade(trade, allowedSlippage, fiatValues.feeUsd),
}) })
function getQuoteMethod(trade: InterfaceTrade) { function getQuoteMethod(trade: InterfaceTrade) {
...@@ -94,10 +102,11 @@ export const formatSwapQuoteReceivedEventProperties = ( ...@@ -94,10 +102,11 @@ export const formatSwapQuoteReceivedEventProperties = (
allowedSlippage: Percent, allowedSlippage: Percent,
swapQuoteLatencyMs: number | undefined, swapQuoteLatencyMs: number | undefined,
inputTax: Percent, inputTax: Percent,
outputTax: Percent outputTax: Percent,
outputFeeFiatValue: number | undefined
) => { ) => {
return { return {
...formatCommonPropertiesForTrade(trade, allowedSlippage), ...formatCommonPropertiesForTrade(trade, allowedSlippage, outputFeeFiatValue),
swap_quote_block_number: isClassicTrade(trade) ? trade.blockNumber : undefined, swap_quote_block_number: isClassicTrade(trade) ? trade.blockNumber : undefined,
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage), allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(), token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(),
......
...@@ -294,6 +294,7 @@ export function Swap({ ...@@ -294,6 +294,7 @@ export function Swap({
inputError: swapInputError, inputError: swapInputError,
inputTax, inputTax,
outputTax, outputTax,
outputFeeFiatValue,
} = swapInfo } = swapInfo
const [inputTokenHasTax, outputTokenHasTax] = useMemo( const [inputTokenHasTax, outputTokenHasTax] = useMemo(
...@@ -441,8 +442,8 @@ export function Swap({ ...@@ -441,8 +442,8 @@ export function Swap({
) )
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
const swapFiatValues = useMemo(() => { const swapFiatValues = useMemo(() => {
return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data } return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data, feeUsd: outputFeeFiatValue }
}, [fiatValueTradeInput, fiatValueTradeOutput]) }, [fiatValueTradeInput.data, fiatValueTradeOutput.data, outputFeeFiatValue])
// the callback to execute the swap // the callback to execute the swap
const swapCallback = useSwapCallback( const swapCallback = useSwapCallback(
...@@ -584,10 +585,17 @@ export function Swap({ ...@@ -584,10 +585,17 @@ export function Swap({
if (!trade || prevTrade === trade) return // no new swap quote to log if (!trade || prevTrade === trade) return // no new swap quote to log
sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_RECEIVED, { sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_RECEIVED, {
...formatSwapQuoteReceivedEventProperties(trade, allowedSlippage, swapQuoteLatency, inputTax, outputTax), ...formatSwapQuoteReceivedEventProperties(
trade,
allowedSlippage,
swapQuoteLatency,
inputTax,
outputTax,
outputFeeFiatValue
),
...trace, ...trace,
}) })
}, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, inputTax, outputTax]) }, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, inputTax, outputTax, outputFeeFiatValue])
const showDetailsDropdown = Boolean( const showDetailsDropdown = Boolean(
!showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing) !showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing)
......
...@@ -35,6 +35,8 @@ const protocols: Protocol[] = [Protocol.V2, Protocol.V3, Protocol.MIXED] ...@@ -35,6 +35,8 @@ const protocols: Protocol[] = [Protocol.V2, Protocol.V3, Protocol.MIXED]
// routing API quote query params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts // routing API quote query params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts
const DEFAULT_QUERY_PARAMS = { const DEFAULT_QUERY_PARAMS = {
protocols, protocols,
// this should be removed once BE fixes issue where enableUniversalRouter is required for fees to work
enableUniversalRouter: true,
} }
function getQuoteLatencyMeasure(mark: PerformanceMark): PerformanceMeasure { function getQuoteLatencyMeasure(mark: PerformanceMark): PerformanceMeasure {
...@@ -66,6 +68,7 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { ...@@ -66,6 +68,7 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig {
const classic = { const classic = {
...DEFAULT_QUERY_PARAMS, ...DEFAULT_QUERY_PARAMS,
routingType: URAQuoteType.CLASSIC, routingType: URAQuoteType.CLASSIC,
recipient: account,
} }
const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets) const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets)
...@@ -124,16 +127,24 @@ export const routingApi = createApi({ ...@@ -124,16 +127,24 @@ export const routingApi = createApi({
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false) logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false)
const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`)
try { try {
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args const {
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT' tokenInAddress: tokenIn,
tokenInChainId,
tokenOutAddress: tokenOut,
tokenOutChainId,
amount,
tradeType,
sendPortionEnabled,
} = args
const requestBody = { const requestBody = {
tokenInChainId, tokenInChainId,
tokenIn: tokenInAddress, tokenIn,
tokenOutChainId, tokenOutChainId,
tokenOut: tokenOutAddress, tokenOut,
amount, amount,
type, sendPortionEnabled,
type: isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT',
intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined, intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined,
configs: getRoutingAPIConfig(args), configs: getRoutingAPIConfig(args),
} }
......
...@@ -50,6 +50,7 @@ export interface GetQuoteArgs { ...@@ -50,6 +50,7 @@ export interface GetQuoteArgs {
// temporary field indicating the user disabled UniswapX during the transition to the opt-out model // temporary field indicating the user disabled UniswapX during the transition to the opt-out model
userOptedOutOfUniswapX: boolean userOptedOutOfUniswapX: boolean
isUniswapXDefaultEnabled: boolean isUniswapXDefaultEnabled: boolean
sendPortionEnabled: boolean
inputTax: Percent inputTax: Percent
outputTax: Percent outputTax: Percent
} }
...@@ -112,11 +113,11 @@ export interface ClassicQuoteData { ...@@ -112,11 +113,11 @@ export interface ClassicQuoteData {
blockNumber: string blockNumber: string
amount: string amount: string
amountDecimals: string amountDecimals: string
gasPriceWei: string gasPriceWei?: string
gasUseEstimate: string gasUseEstimate?: string
gasUseEstimateQuote: string gasUseEstimateQuote?: string
gasUseEstimateQuoteDecimals: string gasUseEstimateQuoteDecimals?: string
gasUseEstimateUSD: string gasUseEstimateUSD?: string
methodParameters?: { calldata: string; value: string } methodParameters?: { calldata: string; value: string }
quote: string quote: string
quoteDecimals: string quoteDecimals: string
...@@ -124,19 +125,30 @@ export interface ClassicQuoteData { ...@@ -124,19 +125,30 @@ export interface ClassicQuoteData {
quoteGasAdjustedDecimals: string quoteGasAdjustedDecimals: string
route: Array<(V3PoolInRoute | V2PoolInRoute)[]> route: Array<(V3PoolInRoute | V2PoolInRoute)[]>
routeString: string routeString: string
portionBips?: number
portionRecipient?: string
portionAmount?: string
portionAmountDecimals?: string
quoteGasAndPortionAdjusted?: string
quoteGasAndPortionAdjustedDecimals?: string
}
export type URADutchOrderQuoteData = {
auctionPeriodSecs: number
deadlineBufferSecs: number
startTimeBufferSecs: number
orderInfo: DutchOrderInfoJSON
quoteId?: string
requestId?: string
slippageTolerance: string
portionBips?: number
portionRecipient?: string
portionAmount?: string
} }
type URADutchOrderQuoteResponse = { type URADutchOrderQuoteResponse = {
routing: URAQuoteType.DUTCH_LIMIT routing: URAQuoteType.DUTCH_LIMIT
quote: { quote: URADutchOrderQuoteData
auctionPeriodSecs: number
deadlineBufferSecs: number
startTimeBufferSecs: number
orderInfo: DutchOrderInfoJSON
quoteId?: string
requestId?: string
slippageTolerance: string
}
allQuotes: Array<URAQuoteResponse> allQuotes: Array<URAQuoteResponse>
} }
type URAClassicQuoteResponse = { type URAClassicQuoteResponse = {
...@@ -179,6 +191,8 @@ export enum TradeFillType { ...@@ -179,6 +191,8 @@ export enum TradeFillType {
export type ApproveInfo = { needsApprove: true; approveGasEstimateUSD: number } | { needsApprove: false } export type ApproveInfo = { needsApprove: true; approveGasEstimateUSD: number } | { needsApprove: false }
export type WrapInfo = { needsWrap: true; wrapGasEstimateUSD: number } | { needsWrap: false } export type WrapInfo = { needsWrap: true; wrapGasEstimateUSD: number } | { needsWrap: false }
export type SwapFeeInfo = { recipient: string; percent: Percent; amount: string /* raw amount of output token */ }
export class ClassicTrade extends Trade<Currency, Currency, TradeType> { export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
public readonly fillType = TradeFillType.Classic public readonly fillType = TradeFillType.Classic
approveInfo: ApproveInfo approveInfo: ApproveInfo
...@@ -189,6 +203,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -189,6 +203,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
quoteMethod: QuoteMethod quoteMethod: QuoteMethod
inputTax: Percent inputTax: Percent
outputTax: Percent outputTax: Percent
swapFee: SwapFeeInfo | undefined
constructor({ constructor({
gasUseEstimateUSD, gasUseEstimateUSD,
...@@ -199,6 +214,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -199,6 +214,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
approveInfo, approveInfo,
inputTax, inputTax,
outputTax, outputTax,
swapFee,
...routes ...routes
}: { }: {
gasUseEstimateUSD?: number gasUseEstimateUSD?: number
...@@ -210,6 +226,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -210,6 +226,7 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
approveInfo: ApproveInfo approveInfo: ApproveInfo
inputTax: Percent inputTax: Percent
outputTax: Percent outputTax: Percent
swapFee?: SwapFeeInfo
v2Routes: { v2Routes: {
routev2: V2Route<Currency, Currency> routev2: V2Route<Currency, Currency>
inputAmount: CurrencyAmount<Currency> inputAmount: CurrencyAmount<Currency>
...@@ -236,17 +253,33 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -236,17 +253,33 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
this.approveInfo = approveInfo this.approveInfo = approveInfo
this.inputTax = inputTax this.inputTax = inputTax
this.outputTax = outputTax this.outputTax = outputTax
this.swapFee = swapFee
}
public get executionPrice(): Price<Currency, Currency> {
if (this.tradeType === TradeType.EXACT_INPUT || !this.swapFee) return super.executionPrice
// Fix inaccurate price calculation for exact output trades
return new Price({ baseAmount: this.inputAmount, quoteAmount: this.postSwapFeeOutputAmount })
} }
public get totalTaxRate(): Percent { public get totalTaxRate(): Percent {
return this.inputTax.add(this.outputTax) return this.inputTax.add(this.outputTax)
} }
public get postSwapFeeOutputAmount(): CurrencyAmount<Currency> {
// Routing api already applies the swap fee to the output amount for exact-in
if (this.tradeType === TradeType.EXACT_INPUT) return this.outputAmount
const swapFeeAmount = CurrencyAmount.fromRawAmount(this.outputAmount.currency, this.swapFee?.amount ?? 0)
return this.outputAmount.subtract(swapFeeAmount)
}
public get postTaxOutputAmount() { public get postTaxOutputAmount() {
// Ideally we should calculate the final output amount by ammending the inputAmount based on the input tax and then applying the output tax, // Ideally we should calculate the final output amount by ammending the inputAmount based on the input tax and then applying the output tax,
// but this isn't currently possible because V2Trade reconstructs the total inputAmount based on the swap routes // but this isn't currently possible because V2Trade reconstructs the total inputAmount based on the swap routes
// TODO(WEB-2761): Amend V2Trade objects in the v2-sdk to have a separate field for post-input tax routes // TODO(WEB-2761): Amend V2Trade objects in the v2-sdk to have a separate field for post-input tax routes
return this.outputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate)) return this.postSwapFeeOutputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate))
} }
public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount<Currency> { public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount<Currency> {
...@@ -281,6 +314,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT ...@@ -281,6 +314,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
inputTax = ZERO_PERCENT inputTax = ZERO_PERCENT
outputTax = ZERO_PERCENT outputTax = ZERO_PERCENT
swapFee: SwapFeeInfo | undefined
constructor({ constructor({
currencyIn, currencyIn,
...@@ -296,6 +330,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT ...@@ -296,6 +330,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
startTimeBufferSecs, startTimeBufferSecs,
deadlineBufferSecs, deadlineBufferSecs,
slippageTolerance, slippageTolerance,
swapFee,
}: { }: {
currencyIn: Currency currencyIn: Currency
currenciesOut: Currency[] currenciesOut: Currency[]
...@@ -310,6 +345,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT ...@@ -310,6 +345,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
startTimeBufferSecs: number startTimeBufferSecs: number
deadlineBufferSecs: number deadlineBufferSecs: number
slippageTolerance: Percent slippageTolerance: Percent
swapFee?: SwapFeeInfo
}) { }) {
super({ currencyIn, currenciesOut, orderInfo, tradeType }) super({ currencyIn, currenciesOut, orderInfo, tradeType })
this.quoteId = quoteId this.quoteId = quoteId
...@@ -321,6 +357,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT ...@@ -321,6 +357,7 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
this.deadlineBufferSecs = deadlineBufferSecs this.deadlineBufferSecs = deadlineBufferSecs
this.slippageTolerance = slippageTolerance this.slippageTolerance = slippageTolerance
this.startTimeBufferSecs = startTimeBufferSecs this.startTimeBufferSecs = startTimeBufferSecs
this.swapFee = swapFee
} }
public get totalGasUseEstimateUSD(): number { public get totalGasUseEstimateUSD(): number {
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { MixedRouteSDK } from '@uniswap/router-sdk' import { MixedRouteSDK } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk' import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk'
import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk' import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
import { BIPS_BASE } from 'constants/misc'
import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens' import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens'
import { toSlippagePercent } from 'utils/slippage' import { toSlippagePercent } from 'utils/slippage'
...@@ -23,9 +24,11 @@ import { ...@@ -23,9 +24,11 @@ import {
QuoteState, QuoteState,
RouterPreference, RouterPreference,
SubmittableTrade, SubmittableTrade,
SwapFeeInfo,
SwapRouterNativeAssets, SwapRouterNativeAssets,
TradeFillType, TradeFillType,
TradeResult, TradeResult,
URADutchOrderQuoteData,
URAQuoteResponse, URAQuoteResponse,
URAQuoteType, URAQuoteType,
V2PoolInRoute, V2PoolInRoute,
...@@ -152,6 +155,18 @@ function getTradeCurrencies(args: GetQuoteArgs | GetQuickQuoteArgs, isUniswapXTr ...@@ -152,6 +155,18 @@ function getTradeCurrencies(args: GetQuoteArgs | GetQuickQuoteArgs, isUniswapXTr
return [currencyIn.isNative ? currencyIn.wrapped : currencyIn, currencyOut] return [currencyIn.isNative ? currencyIn.wrapped : currencyIn, currencyOut]
} }
function getSwapFee(data: ClassicQuoteData | URADutchOrderQuoteData): SwapFeeInfo | undefined {
const { portionAmount, portionBips, portionRecipient } = data
if (!portionAmount || !portionBips || !portionRecipient) return undefined
return {
recipient: portionRecipient,
percent: new Percent(portionBips, BIPS_BASE),
amount: portionAmount,
}
}
function getClassicTradeDetails( function getClassicTradeDetails(
currencyIn: Currency, currencyIn: Currency,
currencyOut: Currency, currencyOut: Currency,
...@@ -161,14 +176,19 @@ function getClassicTradeDetails( ...@@ -161,14 +176,19 @@ function getClassicTradeDetails(
gasUseEstimateUSD?: number gasUseEstimateUSD?: number
blockNumber?: string blockNumber?: string
routes?: RouteResult[] routes?: RouteResult[]
swapFee?: SwapFeeInfo
} { } {
const classicQuote = const classicQuote =
data.routing === URAQuoteType.CLASSIC ? data.quote : data.allQuotes.find(isClassicQuoteResponse)?.quote data.routing === URAQuoteType.CLASSIC ? data.quote : data.allQuotes.find(isClassicQuoteResponse)?.quote
if (!classicQuote) return {}
return { return {
gasUseEstimate: classicQuote?.gasUseEstimate ? parseFloat(classicQuote.gasUseEstimate) : undefined, gasUseEstimate: classicQuote.gasUseEstimate ? parseFloat(classicQuote.gasUseEstimate) : undefined,
gasUseEstimateUSD: classicQuote?.gasUseEstimateUSD ? parseFloat(classicQuote.gasUseEstimateUSD) : undefined, gasUseEstimateUSD: classicQuote.gasUseEstimateUSD ? parseFloat(classicQuote.gasUseEstimateUSD) : undefined,
blockNumber: classicQuote?.blockNumber, blockNumber: classicQuote.blockNumber,
routes: classicQuote ? computeRoutes(currencyIn, currencyOut, classicQuote.route) : undefined, routes: computeRoutes(currencyIn, currencyOut, classicQuote.route),
swapFee: getSwapFee(classicQuote),
} }
} }
...@@ -204,7 +224,7 @@ export async function transformRoutesToTrade( ...@@ -204,7 +224,7 @@ export async function transformRoutesToTrade(
(routerPreference === RouterPreference.X || (isUniswapXDefaultEnabled && routerPreference === RouterPreference.API)) (routerPreference === RouterPreference.X || (isUniswapXDefaultEnabled && routerPreference === RouterPreference.API))
const [currencyIn, currencyOut] = getTradeCurrencies(args, showUniswapXTrade) const [currencyIn, currencyOut] = getTradeCurrencies(args, showUniswapXTrade)
const { gasUseEstimateUSD, blockNumber, routes, gasUseEstimate } = getClassicTradeDetails( const { gasUseEstimateUSD, blockNumber, routes, gasUseEstimate, swapFee } = getClassicTradeDetails(
currencyIn, currencyIn,
currencyOut, currencyOut,
data data
...@@ -254,12 +274,14 @@ export async function transformRoutesToTrade( ...@@ -254,12 +274,14 @@ export async function transformRoutesToTrade(
quoteMethod, quoteMethod,
inputTax, inputTax,
outputTax, outputTax,
swapFee,
}) })
// During the opt-in period, only return UniswapX quotes if the user has turned on the setting, // During the opt-in period, only return UniswapX quotes if the user has turned on the setting,
// even if it is the better quote. // even if it is the better quote.
if (isUniswapXBetter && (routerPreference === RouterPreference.X || isUniswapXDefaultEnabled)) { if (isUniswapXBetter && (routerPreference === RouterPreference.X || isUniswapXDefaultEnabled)) {
const orderInfo = toDutchOrderInfo(data.quote.orderInfo) const orderInfo = toDutchOrderInfo(data.quote.orderInfo)
const swapFee = getSwapFee(data.quote)
const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas) const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas)
const uniswapXTrade = new DutchOrderTrade({ const uniswapXTrade = new DutchOrderTrade({
...@@ -276,6 +298,7 @@ export async function transformRoutesToTrade( ...@@ -276,6 +298,7 @@ export async function transformRoutesToTrade(
startTimeBufferSecs: data.quote.startTimeBufferSecs, startTimeBufferSecs: data.quote.startTimeBufferSecs,
deadlineBufferSecs: data.quote.deadlineBufferSecs, deadlineBufferSecs: data.quote.deadlineBufferSecs,
slippageTolerance: toSlippagePercent(data.quote.slippageTolerance), slippageTolerance: toSlippagePercent(data.quote.slippageTolerance),
swapFee,
}) })
return { return {
......
...@@ -6,13 +6,14 @@ import { useFotAdjustmentsEnabled } from 'featureFlags/flags/fotAdjustments' ...@@ -6,13 +6,14 @@ import { useFotAdjustmentsEnabled } from 'featureFlags/flags/fotAdjustments'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance' import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useDebouncedTrade } from 'hooks/useDebouncedTrade' import { useDebouncedTrade } from 'hooks/useDebouncedTrade'
import { useSwapTaxes } from 'hooks/useSwapTaxes' import { useSwapTaxes } from 'hooks/useSwapTaxes'
import { useUSDPrice } from 'hooks/useUSDPrice'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ParsedQs } from 'qs' import { ParsedQs } from 'qs'
import { ReactNode, useCallback, useEffect, useMemo } from 'react' import { ReactNode, useCallback, useEffect, useMemo } from 'react'
import { AnyAction } from 'redux' import { AnyAction } from 'redux'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { InterfaceTrade, TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import { isClassicTrade, isUniswapXTrade } from 'state/routing/utils' import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routing/utils'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { TOKEN_SHORTHANDS } from '../../constants/tokens' import { TOKEN_SHORTHANDS } from '../../constants/tokens'
...@@ -82,6 +83,7 @@ export type SwapInfo = { ...@@ -82,6 +83,7 @@ export type SwapInfo = {
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> } currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
inputTax: Percent inputTax: Percent
outputTax: Percent outputTax: Percent
outputFeeFiatValue?: number
parsedAmount?: CurrencyAmount<Currency> parsedAmount?: CurrencyAmount<Currency>
inputError?: ReactNode inputError?: ReactNode
trade: { trade: {
...@@ -140,6 +142,13 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine ...@@ -140,6 +142,13 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine
outputTax outputTax
) )
const { data: outputFeeFiatValue } = useUSDPrice(
isSubmittableTrade(trade.trade) && trade.trade.swapFee
? CurrencyAmount.fromRawAmount(trade.trade.outputAmount.currency, trade.trade.swapFee.amount)
: undefined,
trade.trade?.outputAmount.currency
)
const currencyBalances = useMemo( const currencyBalances = useMemo(
() => ({ () => ({
[Field.INPUT]: relevantTokenBalances[0], [Field.INPUT]: relevantTokenBalances[0],
...@@ -215,8 +224,20 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine ...@@ -215,8 +224,20 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine
allowedSlippage, allowedSlippage,
inputTax, inputTax,
outputTax, outputTax,
outputFeeFiatValue,
}), }),
[allowedSlippage, autoSlippage, currencies, currencyBalances, inputError, inputTax, outputTax, parsedAmount, trade] [
allowedSlippage,
autoSlippage,
currencies,
currencyBalances,
inputError,
inputTax,
outputFeeFiatValue,
outputTax,
parsedAmount,
trade,
]
) )
} }
......
...@@ -188,7 +188,7 @@ describe('formatNumber', () => { ...@@ -188,7 +188,7 @@ describe('formatNumber', () => {
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M') expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M')
expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45') expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45')
expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01') expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01')
expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0.00') expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0')
}) })
it('formats gas prices correctly with portugese locale and thai baht currency', () => { it('formats gas prices correctly with portugese locale and thai baht currency', () => {
...@@ -199,7 +199,7 @@ describe('formatNumber', () => { ...@@ -199,7 +199,7 @@ describe('formatNumber', () => {
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('฿\xa01,23\xa0mi') expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('฿\xa01,23\xa0mi')
expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('฿\xa018,45') expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('฿\xa018,45')
expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<฿\xa00,01') expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<฿\xa00,01')
expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('฿\xa00,00') expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('฿\xa00')
}) })
it('formats USD token quantities prices correctly', () => { it('formats USD token quantities prices correctly', () => {
......
...@@ -39,6 +39,14 @@ const NO_DECIMALS: NumberFormatOptions = { ...@@ -39,6 +39,14 @@ const NO_DECIMALS: NumberFormatOptions = {
minimumFractionDigits: 0, minimumFractionDigits: 0,
} }
const NO_DECIMALS_CURRENCY: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 0,
minimumFractionDigits: 0,
currency: 'USD',
style: 'currency',
}
const THREE_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = { const THREE_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'standard', notation: 'standard',
maximumFractionDigits: 3, maximumFractionDigits: 3,
...@@ -262,7 +270,7 @@ const fiatTokenStatsFormatter: FormatterRule[] = [ ...@@ -262,7 +270,7 @@ const fiatTokenStatsFormatter: FormatterRule[] = [
] ]
const fiatGasPriceFormatter: FormatterRule[] = [ const fiatGasPriceFormatter: FormatterRule[] = [
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY }, { exact: 0, formatterOptions: NO_DECIMALS_CURRENCY },
{ upperBound: 0.01, hardCodedInput: { input: 0.01, prefix: '<' }, formatterOptions: TWO_DECIMALS_CURRENCY }, { upperBound: 0.01, hardCodedInput: { input: 0.01, prefix: '<' }, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY }, { upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS }, { upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS },
......
...@@ -6217,10 +6217,10 @@ ...@@ -6217,10 +6217,10 @@
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz#966ba96c9ccc8f0e9e09809890b438203f2b1911" resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz#966ba96c9ccc8f0e9e09809890b438203f2b1911"
integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg== integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg==
"@uniswap/uniswapx-sdk@^1.3.0": "@uniswap/uniswapx-sdk@^1.4.1":
version "1.3.0" version "1.4.1"
resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-1.3.0.tgz#22867580c7f5d5ee35d669444d093e09203e1b47" resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-1.4.1.tgz#c5fc50000032aa714ff0cc4b9cd1957128a2a4ec"
integrity sha512-TXH0+3reXA/liY2IRbCRvPVyREDObKSVmd4vEtTD0sPM0NW6ndSowKDH0hWBj2d7lBnSNKz5fp7IOaFT7yHkug== integrity sha512-M7uuZdozWbXJq8J64KTJ9e0PkYcfe6lx7RBpIsvJaypkGgGDrmU1bAqr1j3sphHlzKTmJCuG7GZBFNcnvzxHLw==
dependencies: dependencies:
"@ethersproject/bytes" "^5.7.0" "@ethersproject/bytes" "^5.7.0"
"@ethersproject/providers" "^5.7.0" "@ethersproject/providers" "^5.7.0"
...@@ -6228,10 +6228,10 @@ ...@@ -6228,10 +6228,10 @@
"@uniswap/sdk-core" "^4.0.3" "@uniswap/sdk-core" "^4.0.3"
ethers "^5.7.0" ethers "^5.7.0"
"@uniswap/universal-router-sdk@^1.5.4", "@uniswap/universal-router-sdk@^1.5.6": "@uniswap/universal-router-sdk@^1.5.4", "@uniswap/universal-router-sdk@^1.5.8":
version "1.5.6" version "1.5.8"
resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.5.6.tgz#274a6ac5df032c34544005fe329aa9e2aac9ade6" resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.5.8.tgz#16c62c3883e99073ba8b6e19188cf418b6551847"
integrity sha512-ZD27U+kugMRJRVEX0oWZsRCw1n5vBN3I17Q22IWE+w/WhOJSppUr6PLo9u4HRdqXTZET7gubnlRc0LOAEkkSkQ== integrity sha512-9tDDBTXarpdRfJStF5mDCNmsQrCfiIT6HCQN1EPq0tAm2b+JzjRkUzsLpbNpVef066FETc3YjPH6JDPB3CMyyA==
dependencies: dependencies:
"@uniswap/permit2-sdk" "^1.2.0" "@uniswap/permit2-sdk" "^1.2.0"
"@uniswap/router-sdk" "^1.6.0" "@uniswap/router-sdk" "^1.6.0"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment