Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac55b1fa2f | ||
|
|
2f6e4d6831 | ||
|
|
00433d55c6 | ||
|
|
0bec35c7ea | ||
|
|
f0e1747bb6 | ||
|
|
8ffa601ab6 | ||
|
|
b887d0f607 | ||
|
|
6160c446ea |
10
.github/workflows/4-deploy-to-prod.yml
vendored
10
.github/workflows/4-deploy-to-prod.yml
vendored
@ -36,6 +36,7 @@ jobs:
|
|||||||
- name: Pin to IPFS
|
- name: Pin to IPFS
|
||||||
id: pinata
|
id: pinata
|
||||||
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
pin-name: Uniswap ${{ steps.github-tag-action.outputs.new_tag }}
|
pin-name: Uniswap ${{ steps.github-tag-action.outputs.new_tag }}
|
||||||
path: './build'
|
path: './build'
|
||||||
@ -45,6 +46,7 @@ jobs:
|
|||||||
- name: Convert CIDv0 to CIDv1
|
- name: Convert CIDv0 to CIDv1
|
||||||
id: convert-cidv0
|
id: convert-cidv0
|
||||||
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
uses: uniswap/convert-cidv0-cidv1@v1.0.0
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||||
|
|
||||||
@ -56,10 +58,6 @@ jobs:
|
|||||||
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
|
tag_name: ${{ steps.github-tag-action.outputs.new_tag }}
|
||||||
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
|
release_name: Release ${{ steps.github-tag-action.outputs.new_tag }}
|
||||||
body: |
|
body: |
|
||||||
IPFS hash of the deployment:
|
|
||||||
- CIDv0: `${{ steps.pinata.outputs.hash }}`
|
|
||||||
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
|
|
||||||
|
|
||||||
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
|
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
|
||||||
|
|
||||||
You can also access the Uniswap Interface from an IPFS gateway.
|
You can also access the Uniswap Interface from an IPFS gateway.
|
||||||
@ -67,6 +65,10 @@ jobs:
|
|||||||
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
|
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
|
||||||
Your Uniswap settings are never remembered across different URLs.
|
Your Uniswap settings are never remembered across different URLs.
|
||||||
|
|
||||||
|
IPFS hashes:
|
||||||
|
- CIDv0: `${{ steps.pinata.outputs.hash }}`
|
||||||
|
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
|
||||||
|
|
||||||
IPFS gateways:
|
IPFS gateways:
|
||||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||||
|
|||||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @uniswap/web-admins
|
||||||
149
cypress/e2e/swap/fees.test.ts
Normal file
149
cypress/e2e/swap/fees.test.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
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%)')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
562
cypress/fixtures/uniswapx/feeQuote.json
Normal file
562
cypress/fixtures/uniswapx/feeQuote.json
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -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"],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -204,8 +204,8 @@
|
|||||||
"@uniswap/sdk-core": "^4.0.3",
|
"@uniswap/sdk-core": "^4.0.3",
|
||||||
"@uniswap/smart-order-router": "^3.15.0",
|
"@uniswap/smart-order-router": "^3.15.0",
|
||||||
"@uniswap/token-lists": "^1.0.0-beta.33",
|
"@uniswap/token-lists": "^1.0.0-beta.33",
|
||||||
"@uniswap/uniswapx-sdk": "^1.3.0",
|
"@uniswap/uniswapx-sdk": "^1.4.1",
|
||||||
"@uniswap/universal-router-sdk": "^1.5.6",
|
"@uniswap/universal-router-sdk": "^1.5.8",
|
||||||
"@uniswap/v2-core": "^1.0.1",
|
"@uniswap/v2-core": "^1.0.1",
|
||||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||||
"@uniswap/v2-sdk": "^3.2.0",
|
"@uniswap/v2-sdk": "^3.2.0",
|
||||||
|
|||||||
@ -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() {
|
|||||||
<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() {
|
|||||||
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,
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
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 }>`
|
|||||||
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({
|
|||||||
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({
|
|||||||
<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'
|
|||||||
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({
|
|||||||
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) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
|||||||
|
|
||||||
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 }) {
|
|||||||
<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 {
|
|||||||
} 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'
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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://support.uniswap.org/hc/en-us/articles/20131678274957">
|
||||||
|
<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> }) {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
</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'
|
|||||||
|
|
||||||
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({
|
|||||||
<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`] = `
|
|||||||
</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"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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
|
|||||||
</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"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ describe('Routing', () => {
|
|||||||
})
|
})
|
||||||
it('contains all coins for optimism', () => {
|
it('contains all coins for optimism', () => {
|
||||||
const symbols = COMMON_BASES[ChainId.OPTIMISM].map((coin) => coin.symbol)
|
const symbols = COMMON_BASES[ChainId.OPTIMISM].map((coin) => coin.symbol)
|
||||||
expect(symbols).toEqual(['ETH', 'OP', 'DAI', 'USDC', 'USDT', 'WBTC'])
|
expect(symbols).toEqual(['ETH', 'OP', 'DAI', 'USDC', 'USDC.e', 'USDT', 'WBTC'])
|
||||||
})
|
})
|
||||||
it('contains all coins for polygon', () => {
|
it('contains all coins for polygon', () => {
|
||||||
const symbols = COMMON_BASES[ChainId.POLYGON].map((coin) => coin.symbol)
|
const symbols = COMMON_BASES[ChainId.POLYGON].map((coin) => coin.symbol)
|
||||||
|
|||||||
@ -27,7 +27,10 @@ import {
|
|||||||
USDC_BSC,
|
USDC_BSC,
|
||||||
USDC_MAINNET,
|
USDC_MAINNET,
|
||||||
USDC_OPTIMISM,
|
USDC_OPTIMISM,
|
||||||
|
USDC_OPTIMISM_GOERLI,
|
||||||
USDC_POLYGON,
|
USDC_POLYGON,
|
||||||
|
USDCe_OPTIMISM,
|
||||||
|
USDCe_OPTIMISM_GOERLI,
|
||||||
USDT,
|
USDT,
|
||||||
USDT_ARBITRUM_ONE,
|
USDT_ARBITRUM_ONE,
|
||||||
USDT_AVALANCHE,
|
USDT_AVALANCHE,
|
||||||
@ -87,9 +90,20 @@ export const COMMON_BASES: ChainCurrencyList = {
|
|||||||
WRAPPED_NATIVE_CURRENCY[ChainId.ARBITRUM_GOERLI] as Token,
|
WRAPPED_NATIVE_CURRENCY[ChainId.ARBITRUM_GOERLI] as Token,
|
||||||
USDC_ARBITRUM_GOERLI,
|
USDC_ARBITRUM_GOERLI,
|
||||||
],
|
],
|
||||||
[ChainId.OPTIMISM]: [nativeOnChain(ChainId.OPTIMISM), OP, DAI_OPTIMISM, USDC_OPTIMISM, USDT_OPTIMISM, WBTC_OPTIMISM],
|
|
||||||
[ChainId.OPTIMISM_GOERLI]: [nativeOnChain(ChainId.OPTIMISM_GOERLI)],
|
[ChainId.OPTIMISM]: [
|
||||||
|
nativeOnChain(ChainId.OPTIMISM),
|
||||||
|
OP,
|
||||||
|
DAI_OPTIMISM,
|
||||||
|
USDC_OPTIMISM,
|
||||||
|
USDCe_OPTIMISM,
|
||||||
|
USDT_OPTIMISM,
|
||||||
|
WBTC_OPTIMISM,
|
||||||
|
],
|
||||||
|
[ChainId.OPTIMISM_GOERLI]: [nativeOnChain(ChainId.OPTIMISM_GOERLI), USDC_OPTIMISM_GOERLI, USDCe_OPTIMISM_GOERLI],
|
||||||
|
|
||||||
[ChainId.BASE]: [nativeOnChain(ChainId.BASE), WRAPPED_NATIVE_CURRENCY[ChainId.BASE] as Token, USDC_BASE],
|
[ChainId.BASE]: [nativeOnChain(ChainId.BASE), WRAPPED_NATIVE_CURRENCY[ChainId.BASE] as Token, USDC_BASE],
|
||||||
|
|
||||||
[ChainId.POLYGON]: [
|
[ChainId.POLYGON]: [
|
||||||
nativeOnChain(ChainId.POLYGON),
|
nativeOnChain(ChainId.POLYGON),
|
||||||
WETH_POLYGON,
|
WETH_POLYGON,
|
||||||
|
|||||||
@ -19,18 +19,32 @@ const USDC_GOERLI = new Token(ChainId.GOERLI, '0x07865c6e87b9f70255377e024ace663
|
|||||||
const USDC_SEPOLIA = new Token(ChainId.SEPOLIA, '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', 6, 'USDC', 'USD//C')
|
const USDC_SEPOLIA = new Token(ChainId.SEPOLIA, '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', 6, 'USDC', 'USD//C')
|
||||||
export const USDC_OPTIMISM = new Token(
|
export const USDC_OPTIMISM = new Token(
|
||||||
ChainId.OPTIMISM,
|
ChainId.OPTIMISM,
|
||||||
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
'0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
|
||||||
6,
|
6,
|
||||||
'USDC',
|
'USDC',
|
||||||
'USD//C'
|
'USD//C'
|
||||||
)
|
)
|
||||||
const USDC_OPTIMISM_GOERLI = new Token(
|
export const USDCe_OPTIMISM = new Token(
|
||||||
|
ChainId.OPTIMISM,
|
||||||
|
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
||||||
|
6,
|
||||||
|
'USDC.e',
|
||||||
|
'Bridged USDC'
|
||||||
|
)
|
||||||
|
export const USDC_OPTIMISM_GOERLI = new Token(
|
||||||
ChainId.OPTIMISM_GOERLI,
|
ChainId.OPTIMISM_GOERLI,
|
||||||
'0x7E07E15D2a87A24492740D16f5bdF58c16db0c4E',
|
'0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6',
|
||||||
6,
|
6,
|
||||||
'USDC',
|
'USDC',
|
||||||
'USD//C'
|
'USD//C'
|
||||||
)
|
)
|
||||||
|
export const USDCe_OPTIMISM_GOERLI = new Token(
|
||||||
|
ChainId.OPTIMISM_GOERLI,
|
||||||
|
'0x7E07E15D2a87A24492740D16f5bdF58c16db0c4E',
|
||||||
|
6,
|
||||||
|
'USDC.e',
|
||||||
|
'Bridged USDC'
|
||||||
|
)
|
||||||
export const USDC_ARBITRUM = new Token(
|
export const USDC_ARBITRUM = new Token(
|
||||||
ChainId.ARBITRUM_ONE,
|
ChainId.ARBITRUM_ONE,
|
||||||
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
||||||
|
|||||||
9
src/featureFlags/flags/useFees.ts
Normal file
9
src/featureFlags/flags/useFees.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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 {
|
|||||||
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 {
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
import { renderHook } from '@testing-library/react'
|
|
||||||
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
|
||||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
|
||||||
import { RouterPreference, TradeState } from 'state/routing/types'
|
|
||||||
import { usePreviewTrade } from 'state/routing/usePreviewTrade'
|
|
||||||
import { useRouterPreference } from 'state/user/hooks'
|
|
||||||
import { mocked } from 'test-utils/mocked'
|
|
||||||
|
|
||||||
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
|
|
||||||
import useAutoRouterSupported from './useAutoRouterSupported'
|
|
||||||
import useDebounce from './useDebounce'
|
|
||||||
import { useDebouncedTrade } from './useDebouncedTrade'
|
|
||||||
import useIsWindowVisible from './useIsWindowVisible'
|
|
||||||
|
|
||||||
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
|
|
||||||
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
|
|
||||||
|
|
||||||
jest.mock('./useAutoRouterSupported')
|
|
||||||
jest.mock('./useDebounce')
|
|
||||||
jest.mock('./useIsWindowVisible')
|
|
||||||
jest.mock('state/routing/useRoutingAPITrade')
|
|
||||||
jest.mock('state/routing/usePreviewTrade')
|
|
||||||
jest.mock('state/user/hooks')
|
|
||||||
|
|
||||||
// helpers to set mock expectations
|
|
||||||
const expectRouterMock = (state: TradeState) => {
|
|
||||||
mocked(useRoutingAPITrade).mockReturnValue({ state, trade: undefined })
|
|
||||||
mocked(usePreviewTrade).mockReturnValue({ state, trade: undefined })
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// ignore debounced value
|
|
||||||
mocked(useDebounce).mockImplementation((value) => value)
|
|
||||||
|
|
||||||
mocked(useIsWindowVisible).mockReturnValue(true)
|
|
||||||
mocked(useAutoRouterSupported).mockReturnValue(true)
|
|
||||||
mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#useBestV3Trade ExactIn', () => {
|
|
||||||
it('does not compute routing api trade when window is not focused', async () => {
|
|
||||||
mocked(useIsWindowVisible).mockReturnValue(false)
|
|
||||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useDebouncedTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
|
||||||
|
|
||||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(
|
|
||||||
/* skipFetch = */ true,
|
|
||||||
TradeType.EXACT_INPUT,
|
|
||||||
USDCAmount,
|
|
||||||
DAI,
|
|
||||||
RouterPreference.API,
|
|
||||||
/* account = */ undefined,
|
|
||||||
/* inputTax = */ undefined,
|
|
||||||
/* outputTax = */ undefined
|
|
||||||
)
|
|
||||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#useDebouncedTrade ExactOut', () => {
|
|
||||||
it('does not compute routing api trade when window is not focused', () => {
|
|
||||||
mocked(useIsWindowVisible).mockReturnValue(false)
|
|
||||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
|
||||||
|
|
||||||
const { result } = renderHook(() => useDebouncedTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
|
||||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(
|
|
||||||
/* skipFetch = */ true,
|
|
||||||
TradeType.EXACT_OUTPUT,
|
|
||||||
DAIAmount,
|
|
||||||
USDC_MAINNET,
|
|
||||||
RouterPreference.API,
|
|
||||||
/* account = */ undefined,
|
|
||||||
/* inputTax = */ undefined,
|
|
||||||
/* outputTax = */ undefined
|
|
||||||
)
|
|
||||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -10,7 +10,6 @@ import { useRouterPreference } from 'state/user/hooks'
|
|||||||
|
|
||||||
import useAutoRouterSupported from './useAutoRouterSupported'
|
import useAutoRouterSupported from './useAutoRouterSupported'
|
||||||
import useDebounce from './useDebounce'
|
import useDebounce from './useDebounce'
|
||||||
import useIsWindowVisible from './useIsWindowVisible'
|
|
||||||
|
|
||||||
// Prevents excessive quote requests between keystrokes.
|
// Prevents excessive quote requests between keystrokes.
|
||||||
const DEBOUNCE_TIME = 350
|
const DEBOUNCE_TIME = 350
|
||||||
@ -71,7 +70,6 @@ export function useDebouncedTrade(
|
|||||||
} {
|
} {
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const autoRouterSupported = useAutoRouterSupported()
|
const autoRouterSupported = useAutoRouterSupported()
|
||||||
const isWindowVisible = useIsWindowVisible()
|
|
||||||
|
|
||||||
const inputs = useMemo<[CurrencyAmount<Currency> | undefined, Currency | undefined]>(
|
const inputs = useMemo<[CurrencyAmount<Currency> | undefined, Currency | undefined]>(
|
||||||
() => [amountSpecified, otherCurrency],
|
() => [amountSpecified, otherCurrency],
|
||||||
@ -94,7 +92,7 @@ export function useDebouncedTrade(
|
|||||||
|
|
||||||
const [routerPreference] = useRouterPreference()
|
const [routerPreference] = useRouterPreference()
|
||||||
|
|
||||||
const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap
|
const skipBothFetches = !autoRouterSupported || isWrap
|
||||||
const skipRoutingFetch = skipBothFetches || isDebouncing
|
const skipRoutingFetch = skipBothFetches || isDebouncing
|
||||||
|
|
||||||
const skipPreviewTradeFetch = skipBothFetches || isPreviewTradeDebouncing
|
const skipPreviewTradeFetch = skipBothFetches || isPreviewTradeDebouncing
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
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'
|
|||||||
|
|
||||||
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(
|
|||||||
slippageTolerance: allowedSlippage,
|
slippageTolerance: allowedSlippage,
|
||||||
deadline,
|
deadline,
|
||||||
permit: permitSignature,
|
permit: permitSignature,
|
||||||
|
...getUniversalRouterFeeFields(trade),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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({
|
|||||||
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({
|
|||||||
.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({
|
|||||||
allowedSlippage,
|
allowedSlippage,
|
||||||
fiatValues,
|
fiatValues,
|
||||||
timeToSignSinceRequestMs: Date.now() - beforeSign,
|
timeToSignSinceRequestMs: Date.now() - beforeSign,
|
||||||
|
portfolioBalanceUsd,
|
||||||
}),
|
}),
|
||||||
...analyticsContext,
|
...analyticsContext,
|
||||||
})
|
})
|
||||||
@ -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({
|
|||||||
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'
|
|||||||
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 {
|
|||||||
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(
|
|||||||
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(
|
|||||||
allowedSlippage: options.slippageTolerance,
|
allowedSlippage: options.slippageTolerance,
|
||||||
fiatValues,
|
fiatValues,
|
||||||
txHash: response.hash,
|
txHash: response.hash,
|
||||||
|
portfolioBalanceUsd,
|
||||||
}),
|
}),
|
||||||
...analyticsContext,
|
...analyticsContext,
|
||||||
})
|
})
|
||||||
@ -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'
|
|||||||
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({
|
|||||||
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({
|
|||||||
uniswapXEthOutputEnabled,
|
uniswapXEthOutputEnabled,
|
||||||
uniswapXExactOutputEnabled,
|
uniswapXExactOutputEnabled,
|
||||||
isUniswapXDefaultEnabled,
|
isUniswapXDefaultEnabled,
|
||||||
|
sendPortionEnabled,
|
||||||
inputTax,
|
inputTax,
|
||||||
outputTax,
|
outputTax,
|
||||||
},
|
},
|
||||||
@ -80,6 +86,7 @@ export function useRoutingAPIArguments({
|
|||||||
userOptedOutOfUniswapX,
|
userOptedOutOfUniswapX,
|
||||||
uniswapXEthOutputEnabled,
|
uniswapXEthOutputEnabled,
|
||||||
isUniswapXDefaultEnabled,
|
isUniswapXDefaultEnabled,
|
||||||
|
sendPortionEnabled,
|
||||||
inputTax,
|
inputTax,
|
||||||
outputTax,
|
outputTax,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
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) {
|
|||||||
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
|
|||||||
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
|
|||||||
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 = ({
|
|||||||
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 = (
|
|||||||
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(),
|
||||||
|
|||||||
3734
src/locales/af-ZA.po
Normal file
3734
src/locales/af-ZA.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ar-SA.po
Normal file
3734
src/locales/ar-SA.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ca-ES.po
Normal file
3734
src/locales/ca-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/cs-CZ.po
Normal file
3734
src/locales/cs-CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/da-DK.po
Normal file
3734
src/locales/da-DK.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/de-DE.po
Normal file
3734
src/locales/de-DE.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/el-GR.po
Normal file
3734
src/locales/el-GR.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/es-ES.po
Normal file
3734
src/locales/es-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/fi-FI.po
Normal file
3734
src/locales/fi-FI.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/fr-FR.po
Normal file
3734
src/locales/fr-FR.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/he-IL.po
Normal file
3734
src/locales/he-IL.po
Normal file
File diff suppressed because it is too large
Load Diff
3735
src/locales/hu-HU.po
Normal file
3735
src/locales/hu-HU.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/id-ID.po
Normal file
3734
src/locales/id-ID.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/it-IT.po
Normal file
3734
src/locales/it-IT.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ja-JP.po
Normal file
3734
src/locales/ja-JP.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ko-KR.po
Normal file
3734
src/locales/ko-KR.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/nl-NL.po
Normal file
3734
src/locales/nl-NL.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/no-NO.po
Normal file
3734
src/locales/no-NO.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/pl-PL.po
Normal file
3734
src/locales/pl-PL.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/pt-BR.po
Normal file
3734
src/locales/pt-BR.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/pt-PT.po
Normal file
3734
src/locales/pt-PT.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ro-RO.po
Normal file
3734
src/locales/ro-RO.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/ru-RU.po
Normal file
3734
src/locales/ru-RU.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/sl-SI.po
Normal file
3734
src/locales/sl-SI.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/sr-SP.po
Normal file
3734
src/locales/sr-SP.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/sv-SE.po
Normal file
3734
src/locales/sv-SE.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/sw-TZ.po
Normal file
3734
src/locales/sw-TZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/th-TH.po
Normal file
3734
src/locales/th-TH.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/tr-TR.po
Normal file
3734
src/locales/tr-TR.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/uk-UA.po
Normal file
3734
src/locales/uk-UA.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/vi-VN.po
Normal file
3734
src/locales/vi-VN.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/zh-CN.po
Normal file
3734
src/locales/zh-CN.po
Normal file
File diff suppressed because it is too large
Load Diff
3734
src/locales/zh-TW.po
Normal file
3734
src/locales/zh-TW.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -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({
|
|||||||
)
|
)
|
||||||
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({
|
|||||||
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)
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { useAllLists } from 'state/lists/hooks'
|
|||||||
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
||||||
import useIsWindowVisible from '../../hooks/useIsWindowVisible'
|
import useIsWindowVisible from '../../hooks/useIsWindowVisible'
|
||||||
import { acceptListUpdate } from './actions'
|
import { acceptListUpdate } from './actions'
|
||||||
import { shouldAcceptVersionUpdate } from './utils'
|
|
||||||
|
|
||||||
export default function Updater(): null {
|
export default function Updater(): null {
|
||||||
const { provider } = useWeb3React()
|
const { provider } = useWeb3React()
|
||||||
@ -61,7 +60,7 @@ export default function Updater(): null {
|
|||||||
})
|
})
|
||||||
}, [dispatch, fetchList, lists, rehydrated])
|
}, [dispatch, fetchList, lists, rehydrated])
|
||||||
|
|
||||||
// automatically update lists if versions are minor/patch
|
// automatically update lists for every version update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Object.keys(lists).forEach((listUrl) => {
|
Object.keys(lists).forEach((listUrl) => {
|
||||||
const list = lists[listUrl]
|
const list = lists[listUrl]
|
||||||
@ -71,13 +70,7 @@ export default function Updater(): null {
|
|||||||
case VersionUpgrade.NONE:
|
case VersionUpgrade.NONE:
|
||||||
throw new Error('unexpected no version bump')
|
throw new Error('unexpected no version bump')
|
||||||
case VersionUpgrade.PATCH:
|
case VersionUpgrade.PATCH:
|
||||||
case VersionUpgrade.MINOR: {
|
case VersionUpgrade.MINOR:
|
||||||
if (shouldAcceptVersionUpdate(listUrl, list.current, list.pendingUpdate, bump)) {
|
|
||||||
dispatch(acceptListUpdate(listUrl))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// update any active or inactive lists
|
|
||||||
case VersionUpgrade.MAJOR:
|
case VersionUpgrade.MAJOR:
|
||||||
dispatch(acceptListUpdate(listUrl))
|
dispatch(acceptListUpdate(listUrl))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import { TokenList, VersionUpgrade } from '@uniswap/token-lists'
|
|
||||||
|
|
||||||
import { shouldAcceptVersionUpdate } from './utils'
|
|
||||||
|
|
||||||
function buildTokenList(count: number): TokenList {
|
|
||||||
const tokens = []
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
tokens.push({
|
|
||||||
name: `Token ${i}`,
|
|
||||||
address: `0x${i.toString().padStart(40, '0')}`,
|
|
||||||
symbol: `T${i}`,
|
|
||||||
decimals: 18,
|
|
||||||
chainId: 1,
|
|
||||||
logoURI: `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x${i
|
|
||||||
.toString()
|
|
||||||
.padStart(40, '0')}/logo.png`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: 'Defi',
|
|
||||||
logoURI:
|
|
||||||
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png',
|
|
||||||
keywords: ['defi', 'uniswap'],
|
|
||||||
timestamp: '2021-03-12T00:00:00.000Z',
|
|
||||||
version: {
|
|
||||||
major: 1,
|
|
||||||
minor: 0,
|
|
||||||
patch: 0,
|
|
||||||
},
|
|
||||||
tokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('shouldAcceptMinorVersionUpdate', () => {
|
|
||||||
it('returns false for patch when tokens have changed', () => {
|
|
||||||
jest.spyOn(console, 'debug').mockReturnValue(undefined)
|
|
||||||
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.PATCH)).toEqual(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
expect(console.debug).toHaveBeenCalledWith(expect.stringMatching(/should have been MAJOR/))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns true for patch when tokens are the same', () => {
|
|
||||||
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(1), VersionUpgrade.PATCH)).toEqual(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns true for minor version bump with tokens added', () => {
|
|
||||||
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.MINOR)).toEqual(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns true for no version bump', () => {
|
|
||||||
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.MINOR)).toEqual(
|
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns false for minor version bump with tokens removed', () => {
|
|
||||||
jest.spyOn(console, 'debug').mockReturnValue(undefined)
|
|
||||||
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(2), buildTokenList(1), VersionUpgrade.MINOR)).toEqual(
|
|
||||||
false
|
|
||||||
)
|
|
||||||
expect(console.debug).toHaveBeenCalledWith(expect.stringMatching(/should have been MAJOR/))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { minVersionBump, TokenList, VersionUpgrade } from '@uniswap/token-lists'
|
|
||||||
|
|
||||||
export function shouldAcceptVersionUpdate(
|
|
||||||
listUrl: string,
|
|
||||||
current: TokenList,
|
|
||||||
update: TokenList,
|
|
||||||
targetBump: VersionUpgrade.PATCH | VersionUpgrade.MINOR
|
|
||||||
): boolean {
|
|
||||||
const min = minVersionBump(current.tokens, update.tokens)
|
|
||||||
// Automatically update minor/patch as long as bump matches the min update.
|
|
||||||
if (targetBump >= min) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
console.debug(
|
|
||||||
`List at url ${listUrl} could not automatically update because the version bump was only PATCH/MINOR while the update had breaking changes and should have been MAJOR`
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
74
src/state/migrations/3.test.ts
Normal file
74
src/state/migrations/3.test.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { ChainId, Token } from '@uniswap/sdk-core'
|
||||||
|
import { createMigrate } from 'redux-persist'
|
||||||
|
import { RouterPreference } from 'state/routing/types'
|
||||||
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
|
|
||||||
|
import { migration1 } from './1'
|
||||||
|
import { migration2 } from './2'
|
||||||
|
import { migration3, PersistAppStateV3 } from './3'
|
||||||
|
|
||||||
|
const previousState: PersistAppStateV3 = {
|
||||||
|
user: {
|
||||||
|
userLocale: null,
|
||||||
|
userRouterPreference: RouterPreference.API,
|
||||||
|
userHideClosedPositions: false,
|
||||||
|
userSlippageTolerance: SlippageTolerance.Auto,
|
||||||
|
userSlippageToleranceHasBeenMigratedToAuto: true,
|
||||||
|
userDeadline: 1800,
|
||||||
|
tokens: {
|
||||||
|
// wrong tokens
|
||||||
|
[ChainId.OPTIMISM]: {
|
||||||
|
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607': new Token(
|
||||||
|
ChainId.OPTIMISM,
|
||||||
|
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
||||||
|
6,
|
||||||
|
'USDC',
|
||||||
|
'USD Coin'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[ChainId.BASE]: {
|
||||||
|
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA': new Token(
|
||||||
|
ChainId.BASE,
|
||||||
|
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
|
||||||
|
6,
|
||||||
|
'USDC',
|
||||||
|
'USD Coin'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pairs: {},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
hideBaseWalletBanner: false,
|
||||||
|
},
|
||||||
|
_persist: {
|
||||||
|
version: 2,
|
||||||
|
rehydrated: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('migration to v3', () => {
|
||||||
|
it('should migrate users who currently have outdated USDC.e saved', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
2: migration2,
|
||||||
|
3: migration3,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(previousState, 3)
|
||||||
|
expect(Object.keys(result?.user?.tokens).length).toEqual(2)
|
||||||
|
expect(result?.user?.tokens[ChainId.OPTIMISM]?.['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'].symbol).toEqual(
|
||||||
|
'USDC.e'
|
||||||
|
)
|
||||||
|
expect(result?.user?.tokens[ChainId.OPTIMISM]?.['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'].name).toEqual(
|
||||||
|
'Bridged USDC'
|
||||||
|
)
|
||||||
|
expect(result?.user?.tokens[ChainId.BASE]?.['0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA'].symbol).toEqual('USDbC')
|
||||||
|
expect(result?.user?.tokens[ChainId.BASE]?.['0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA'].name).toEqual(
|
||||||
|
'USD Base Coin'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result?._persist.version).toEqual(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
55
src/state/migrations/3.ts
Normal file
55
src/state/migrations/3.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Token } from '@uniswap/sdk-core'
|
||||||
|
import { ChainId } from '@uniswap/sdk-core'
|
||||||
|
import { PersistState } from 'redux-persist'
|
||||||
|
import { serializeToken } from 'state/user/hooks'
|
||||||
|
import { UserState } from 'state/user/reducer'
|
||||||
|
|
||||||
|
export type PersistAppStateV3 = {
|
||||||
|
_persist: PersistState
|
||||||
|
} & { user?: UserState }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration to clear users' imported token lists, after
|
||||||
|
* breaking changes to token info for multichain native USDC.
|
||||||
|
*/
|
||||||
|
export const migration3 = (state: PersistAppStateV3 | undefined) => {
|
||||||
|
if (state?.user) {
|
||||||
|
// Update USDC.e tokens to use the the new USDC.e symbol (from USDC)
|
||||||
|
const USDCe_ADDRESSES: { [key in ChainId]?: string } = {
|
||||||
|
[ChainId.OPTIMISM]: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
|
||||||
|
[ChainId.OPTIMISM_GOERLI]: '0x7E07E15D2a87A24492740D16f5bdF58c16db0c4E',
|
||||||
|
[ChainId.ARBITRUM_ONE]: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
|
||||||
|
[ChainId.ARBITRUM_GOERLI]: '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892',
|
||||||
|
[ChainId.AVALANCHE]: '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664',
|
||||||
|
[ChainId.POLYGON]: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
|
||||||
|
[ChainId.POLYGON_MUMBAI]: '0xe11a86849d99f524cac3e7a0ec1241828e332c62',
|
||||||
|
}
|
||||||
|
for (const [chainId, address] of Object.entries(USDCe_ADDRESSES)) {
|
||||||
|
const chainIdKey = Number(chainId) as ChainId
|
||||||
|
if (state.user.tokens[chainIdKey]?.[address]) {
|
||||||
|
state.user.tokens[chainIdKey][address] = serializeToken(
|
||||||
|
new Token(chainIdKey, address, 6, 'USDC.e', 'Bridged USDC')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update USDbC token to use the new USDbC symbol (from USDC)
|
||||||
|
const USDbC_BASE = new Token(
|
||||||
|
ChainId.BASE,
|
||||||
|
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
|
||||||
|
6,
|
||||||
|
'USDbC',
|
||||||
|
'USD Base Coin'
|
||||||
|
)
|
||||||
|
if (state.user.tokens[ChainId.BASE]?.[USDbC_BASE.address]) {
|
||||||
|
state.user.tokens[ChainId.BASE][USDbC_BASE.address] = serializeToken(USDbC_BASE)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
_persist: {
|
||||||
|
...state._persist,
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
@ -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 {
|
|||||||
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({
|
|||||||
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 {
|
|||||||
// 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 {
|
|||||||
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,11 +125,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
type URADutchOrderQuoteResponse = {
|
export type URADutchOrderQuoteData = {
|
||||||
routing: URAQuoteType.DUTCH_LIMIT
|
|
||||||
quote: {
|
|
||||||
auctionPeriodSecs: number
|
auctionPeriodSecs: number
|
||||||
deadlineBufferSecs: number
|
deadlineBufferSecs: number
|
||||||
startTimeBufferSecs: number
|
startTimeBufferSecs: number
|
||||||
@ -136,7 +141,14 @@ type URADutchOrderQuoteResponse = {
|
|||||||
quoteId?: string
|
quoteId?: string
|
||||||
requestId?: string
|
requestId?: string
|
||||||
slippageTolerance: string
|
slippageTolerance: string
|
||||||
|
portionBips?: number
|
||||||
|
portionRecipient?: string
|
||||||
|
portionAmount?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URADutchOrderQuoteResponse = {
|
||||||
|
routing: URAQuoteType.DUTCH_LIMIT
|
||||||
|
quote: URADutchOrderQuoteData
|
||||||
allQuotes: Array<URAQuoteResponse>
|
allQuotes: Array<URAQuoteResponse>
|
||||||
}
|
}
|
||||||
type URAClassicQuoteResponse = {
|
type URAClassicQuoteResponse = {
|
||||||
@ -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> {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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 {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { skipToken } from '@reduxjs/toolkit/query/react'
|
|||||||
import { ChainId, Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
import { ChainId, Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
import { ZERO_PERCENT } from 'constants/misc'
|
import { ZERO_PERCENT } from 'constants/misc'
|
||||||
import { useQuickRouteMainnetEnabled } from 'featureFlags/flags/quickRouteMainnet'
|
import { useQuickRouteMainnetEnabled } from 'featureFlags/flags/quickRouteMainnet'
|
||||||
|
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { useGetQuickRouteQuery, useGetQuickRouteQueryState } from './quickRouteSlice'
|
import { useGetQuickRouteQuery, useGetQuickRouteQueryState } from './quickRouteSlice'
|
||||||
@ -78,9 +79,10 @@ export function usePreviewTrade(
|
|||||||
inputTax,
|
inputTax,
|
||||||
outputTax,
|
outputTax,
|
||||||
})
|
})
|
||||||
|
const isWindowVisible = useIsWindowVisible()
|
||||||
|
|
||||||
const { isError, data: tradeResult, error, currentData } = useGetQuickRouteQueryState(queryArgs)
|
const { isError, data: tradeResult, error, currentData } = useGetQuickRouteQueryState(queryArgs)
|
||||||
useGetQuickRouteQuery(skipFetch ? skipToken : queryArgs, {
|
useGetQuickRouteQuery(skipFetch || !isWindowVisible ? skipToken : queryArgs, {
|
||||||
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
||||||
refetchOnMountOrArgChange: 2 * 60,
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
})
|
})
|
||||||
|
|||||||
143
src/state/routing/useRoutingAPITrade.test.ts
Normal file
143
src/state/routing/useRoutingAPITrade.test.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { skipToken } from '@reduxjs/toolkit/query/react'
|
||||||
|
import { renderHook } from '@testing-library/react'
|
||||||
|
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||||
|
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
||||||
|
import { ZERO_PERCENT } from 'constants/misc'
|
||||||
|
import { USDC_MAINNET } from 'constants/tokens'
|
||||||
|
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
||||||
|
import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput'
|
||||||
|
import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput'
|
||||||
|
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
|
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
||||||
|
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||||
|
import ms from 'ms'
|
||||||
|
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
||||||
|
import { useRouterPreference, useUserDisabledUniswapX, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
||||||
|
import { ETH_MAINNET } from 'test-utils/constants'
|
||||||
|
import { mocked } from 'test-utils/mocked'
|
||||||
|
|
||||||
|
import { useGetQuoteQuery, useGetQuoteQueryState } from './slice'
|
||||||
|
import { useRoutingAPITrade } from './useRoutingAPITrade'
|
||||||
|
import { currencyAddressForSwapQuote } from './utils'
|
||||||
|
|
||||||
|
const USDCAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '10000')
|
||||||
|
|
||||||
|
jest.mock('hooks/useIsWindowVisible')
|
||||||
|
jest.mock('state/routing/usePreviewTrade')
|
||||||
|
jest.mock('./slice', () => {
|
||||||
|
return {
|
||||||
|
useGetQuoteQuery: jest.fn(),
|
||||||
|
useGetQuoteQueryState: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
jest.mock('state/user/hooks')
|
||||||
|
jest.mock('featureFlags/flags/uniswapXUseSyntheticQuote')
|
||||||
|
jest.mock('featureFlags/flags/uniswapXEthOutput')
|
||||||
|
jest.mock('featureFlags/flags/uniswapXExactOutput')
|
||||||
|
jest.mock('featureFlags/flags/uniswapXDefault')
|
||||||
|
jest.mock('featureFlags/flags/useFees')
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||||
|
mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
|
||||||
|
mocked(useUniswapXSyntheticQuoteEnabled).mockReturnValue(false)
|
||||||
|
mocked(useUserDisabledUniswapX).mockReturnValue(false)
|
||||||
|
mocked(useUserOptedOutOfUniswapX).mockReturnValue(false)
|
||||||
|
mocked(useUniswapXEthOutputEnabled).mockReturnValue(false)
|
||||||
|
mocked(useUniswapXExactOutputEnabled).mockReturnValue(false)
|
||||||
|
mocked(useUniswapXDefaultEnabled).mockReturnValue(false)
|
||||||
|
mocked(useFeesEnabled).mockReturnValue(true)
|
||||||
|
// @ts-ignore we dont use the response from this hook in useRoutingAPITrade so fine to mock as undefined
|
||||||
|
mocked(useGetQuoteQuery).mockReturnValue(undefined)
|
||||||
|
mocked(useGetQuoteQueryState).mockReturnValue({
|
||||||
|
refetch: jest.fn(),
|
||||||
|
isError: false,
|
||||||
|
data: undefined,
|
||||||
|
error: false,
|
||||||
|
currentData: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const MOCK_ARGS: GetQuoteArgs = {
|
||||||
|
account: undefined,
|
||||||
|
amount: USDCAmount.quotient.toString(),
|
||||||
|
tokenInAddress: currencyAddressForSwapQuote(USDCAmount.currency),
|
||||||
|
tokenInChainId: USDCAmount.currency.chainId,
|
||||||
|
tokenInDecimals: USDCAmount.currency.wrapped.decimals,
|
||||||
|
tokenInSymbol: USDCAmount.currency.wrapped.symbol,
|
||||||
|
tokenOutAddress: currencyAddressForSwapQuote(ETH_MAINNET),
|
||||||
|
tokenOutChainId: ETH_MAINNET.wrapped.chainId,
|
||||||
|
tokenOutDecimals: ETH_MAINNET.wrapped.decimals,
|
||||||
|
tokenOutSymbol: ETH_MAINNET.wrapped.symbol,
|
||||||
|
routerPreference: RouterPreference.API,
|
||||||
|
tradeType: TradeType.EXACT_INPUT,
|
||||||
|
needsWrapIfUniswapX: USDCAmount.currency.isNative,
|
||||||
|
uniswapXForceSyntheticQuotes: false,
|
||||||
|
userDisabledUniswapX: false,
|
||||||
|
userOptedOutOfUniswapX: false,
|
||||||
|
uniswapXEthOutputEnabled: false,
|
||||||
|
uniswapXExactOutputEnabled: false,
|
||||||
|
isUniswapXDefaultEnabled: false,
|
||||||
|
sendPortionEnabled: true,
|
||||||
|
inputTax: ZERO_PERCENT,
|
||||||
|
outputTax: ZERO_PERCENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('#useRoutingAPITrade ExactIn', () => {
|
||||||
|
it('does not call routing api when window is not focused for quote requests', () => {
|
||||||
|
mocked(useIsWindowVisible).mockReturnValue(false)
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useRoutingAPITrade(false, TradeType.EXACT_INPUT, USDCAmount, ETH_MAINNET, RouterPreference.API)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(useGetQuoteQuery).toHaveBeenCalledWith(skipToken, {
|
||||||
|
pollingInterval: AVERAGE_L1_BLOCK_TIME,
|
||||||
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
|
})
|
||||||
|
expect(result.current?.trade).toEqual(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does call routing api when window is focused for quote requests', () => {
|
||||||
|
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||||
|
|
||||||
|
renderHook(() => useRoutingAPITrade(false, TradeType.EXACT_INPUT, USDCAmount, ETH_MAINNET, RouterPreference.API))
|
||||||
|
|
||||||
|
expect(useGetQuoteQuery).toHaveBeenCalledWith(MOCK_ARGS, {
|
||||||
|
pollingInterval: AVERAGE_L1_BLOCK_TIME,
|
||||||
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#useRoutingAPITrade pricing', () => {
|
||||||
|
it('does not call routing api when window is not focused for price requests', () => {
|
||||||
|
mocked(useIsWindowVisible).mockReturnValue(false)
|
||||||
|
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useRoutingAPITrade(false, TradeType.EXACT_INPUT, USDCAmount, ETH_MAINNET, INTERNAL_ROUTER_PREFERENCE_PRICE)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(useGetQuoteQuery).toHaveBeenCalledWith(skipToken, {
|
||||||
|
pollingInterval: ms(`1m`),
|
||||||
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
|
})
|
||||||
|
expect(result.current?.trade).toEqual(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does call routing api when window is focused for pricing requests', () => {
|
||||||
|
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||||
|
|
||||||
|
renderHook(() =>
|
||||||
|
useRoutingAPITrade(false, TradeType.EXACT_INPUT, USDCAmount, ETH_MAINNET, INTERNAL_ROUTER_PREFERENCE_PRICE)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(useGetQuoteQuery).toHaveBeenCalledWith(
|
||||||
|
{ ...MOCK_ARGS, sendPortionEnabled: false, routerPreference: INTERNAL_ROUTER_PREFERENCE_PRICE },
|
||||||
|
{
|
||||||
|
pollingInterval: ms(`1m`),
|
||||||
|
refetchOnMountOrArgChange: 2 * 60,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -2,6 +2,7 @@ import { skipToken } from '@reduxjs/toolkit/query/react'
|
|||||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
||||||
import { ZERO_PERCENT } from 'constants/misc'
|
import { ZERO_PERCENT } from 'constants/misc'
|
||||||
|
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||||
import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments'
|
import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments'
|
||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
@ -92,9 +93,11 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
|
|||||||
inputTax,
|
inputTax,
|
||||||
outputTax,
|
outputTax,
|
||||||
})
|
})
|
||||||
|
// skip all pricing and quote requests if the window is not focused
|
||||||
|
const isWindowVisible = useIsWindowVisible()
|
||||||
|
|
||||||
const { isError, data: tradeResult, error, currentData } = useGetQuoteQueryState(queryArgs)
|
const { isError, data: tradeResult, error, currentData } = useGetQuoteQueryState(queryArgs)
|
||||||
useGetQuoteQuery(skipFetch ? skipToken : queryArgs, {
|
useGetQuoteQuery(skipFetch || !isWindowVisible ? skipToken : queryArgs, {
|
||||||
// Price-fetching is informational and costly, so it's done less frequently.
|
// Price-fetching is informational and costly, so it's done less frequently.
|
||||||
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms(`1m`) : AVERAGE_L1_BLOCK_TIME,
|
pollingInterval: routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? ms(`1m`) : AVERAGE_L1_BLOCK_TIME,
|
||||||
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
// If latest quote from cache was fetched > 2m ago, instantly repoll for another instead of waiting for next poll period
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
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 {
|
|||||||
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
|
|||||||
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(
|
|||||||
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(
|
|||||||
(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(
|
|||||||
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(
|
|||||||
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'
|
|||||||
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 = {
|
|||||||
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
|
|||||||
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
|
|||||||
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', () => {
|
|||||||
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', () => {
|
|||||||
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 = {
|
|||||||
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[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
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 },
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@ -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 @@
|
|||||||
"@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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user