Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c689e1d4 | ||
|
|
70cd7272a1 | ||
|
|
2b5769ac86 | ||
|
|
b811afd134 | ||
|
|
6131e6bfab | ||
|
|
5979635939 | ||
|
|
5399bdb550 | ||
|
|
1df9da9eff | ||
|
|
b1e6d0ab7a | ||
|
|
a7fcbb4cfc | ||
|
|
a5a6a037e5 | ||
|
|
8bfebd37a2 | ||
|
|
4029819090 | ||
|
|
021ae5e74e | ||
|
|
2b9720705f | ||
|
|
8f1ea32e5e | ||
|
|
23acb3b395 | ||
|
|
772416cc7a | ||
|
|
f15e5725f1 | ||
|
|
83c8393f19 | ||
|
|
1348eb3322 | ||
|
|
a2271ba428 | ||
|
|
1845cb3b7b | ||
|
|
6a02bde8e0 | ||
|
|
c18522159b | ||
|
|
5ea7b1de3f | ||
|
|
6efe8f3260 | ||
|
|
9ac28a4571 | ||
|
|
bde1421ffb | ||
|
|
3dceb45d9e | ||
|
|
7b589561bc | ||
|
|
c9d3dc36b8 | ||
|
|
ef4d8fc269 | ||
|
|
ff9cc5cb69 | ||
|
|
719fd524ed | ||
|
|
f15dd1e61e | ||
|
|
8c372c6142 | ||
|
|
8c0998bd59 | ||
|
|
ad3a4ea808 | ||
|
|
d30c5173f5 | ||
|
|
689100afa2 | ||
|
|
d546ffec1c | ||
|
|
4a72d8835c | ||
|
|
19c6023601 | ||
|
|
a4a954c8af | ||
|
|
82dcdcec55 | ||
|
|
27e20d7230 | ||
|
|
95eafbab7d | ||
|
|
639fe2f73b | ||
|
|
72cd27f045 | ||
|
|
4e64c0e88f | ||
|
|
fda28d9be3 | ||
|
|
bc92af6c15 | ||
|
|
9a257e0ca8 | ||
|
|
82646b77dd | ||
|
|
1992c5de06 | ||
|
|
0208ccd7d2 | ||
|
|
12df4b3981 | ||
|
|
3eaeb65b07 | ||
|
|
6df2f3677e | ||
|
|
80edf5a0d6 | ||
|
|
96f6929127 | ||
|
|
4ec95d0927 | ||
|
|
fba6cc9e02 | ||
|
|
bc2f68565b | ||
|
|
f232643d8e | ||
|
|
527270e33f | ||
|
|
18cd5ec9d9 | ||
|
|
5ddb565805 | ||
|
|
f0b4b92b88 | ||
|
|
4d82f9fb3a | ||
|
|
654b26dc54 | ||
|
|
c0753ae52f | ||
|
|
16bb9470ae | ||
|
|
6dcfca24cb | ||
|
|
9cac9f8299 | ||
|
|
5def0dd166 | ||
|
|
7229637c4c | ||
|
|
f26b09537d | ||
|
|
8f922b665a | ||
|
|
134b1d708f | ||
|
|
e9bddcb670 | ||
|
|
19e45fd119 | ||
|
|
ae4135fa49 | ||
|
|
89e438bcc5 | ||
|
|
92af2167ee | ||
|
|
db6084d717 | ||
|
|
927d35d59e | ||
|
|
b4e981b2fd | ||
|
|
967a698178 | ||
|
|
7818426b53 | ||
|
|
93e0054f10 | ||
|
|
661d2b6a33 | ||
|
|
c560b94366 | ||
|
|
93a4f00287 | ||
|
|
48833f27e3 | ||
|
|
35a03e2681 | ||
|
|
ac0badfb1d | ||
|
|
149b18f02e | ||
|
|
52a43f3db0 | ||
|
|
0a2a46d506 | ||
|
|
a7c1bd4391 | ||
|
|
13221e6935 | ||
|
|
26fc3caa55 | ||
|
|
6072bb1be0 | ||
|
|
302af21a22 | ||
|
|
b61a2d4111 | ||
|
|
9be26788a2 | ||
|
|
ed393de481 | ||
|
|
cf5c393d97 | ||
|
|
68d81a0040 | ||
|
|
53caa51ac3 | ||
|
|
409ba72f9f | ||
|
|
9d9b3dca78 | ||
|
|
a11c7e9573 | ||
|
|
31bbcae1ed | ||
|
|
a1f6c7270e | ||
|
|
8471d9b46f | ||
|
|
5fc4d98faa | ||
|
|
8d9ddf36a2 | ||
|
|
6cfd5fa475 | ||
|
|
f2c5a7c09c | ||
|
|
fb52770953 | ||
|
|
94aa8ae2c9 | ||
|
|
6cb0824a0b | ||
|
|
777887b25d | ||
|
|
d15d5d85f5 |
1
.env
1
.env
@@ -1,5 +1,6 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
|
||||
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
timeout-minutes: 2
|
||||
with:
|
||||
cid: ${{ steps.upload.outputs.hash }}
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
seeds: ${{ secrets.CRUST_SEEDS }}
|
||||
|
||||
- name: Convert CIDv0 to CIDv1
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
IPFS gateways:
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
|
||||
- [ipfs://${{ steps.pinata.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
|
||||
|
||||
${{ needs.tag.outputs.changelog }}
|
||||
|
||||
|
||||
27
.github/workflows/revert.yaml
vendored
27
.github/workflows/revert.yaml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Revert
|
||||
on:
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
- run: yarn build
|
||||
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@@ -30,6 +30,11 @@ jobs:
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
- run: yarn test
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
cypress-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
10
README.md
10
README.md
@@ -1,5 +1,7 @@
|
||||
# Uniswap Labs Interface
|
||||
|
||||
[](https://codecov.io/gh/Uniswap/interface)
|
||||
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
|
||||
[](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
|
||||
@@ -40,10 +42,10 @@ For steps on local deployment, development, and code contribution, please see [C
|
||||
|
||||
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
|
||||
|
||||
- Swap on Uniswap V2: https://app.uniswap.org/#/swap?use=v2
|
||||
- View V2 liquidity: https://app.uniswap.org/#/pool/v2
|
||||
- Add V2 liquidity: https://app.uniswap.org/#/add/v2
|
||||
- Migrate V2 liquidity to V3: https://app.uniswap.org/#/migrate/v2
|
||||
- Swap on Uniswap V2: <https://app.uniswap.org/#/swap?use=v2>
|
||||
- View V2 liquidity: <https://app.uniswap.org/#/pool/v2>
|
||||
- Add V2 liquidity: <https://app.uniswap.org/#/add/v2>
|
||||
- Migrate V2 liquidity to V3: <https://app.uniswap.org/#/migrate/v2>
|
||||
|
||||
## Accessing Uniswap V1
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ module.exports = {
|
||||
jest: {
|
||||
configure(jestConfig) {
|
||||
return Object.assign({}, jestConfig, {
|
||||
transformIgnorePatterns: ['@uniswap/conedison/format'],
|
||||
transformIgnorePatterns: ['@uniswap/conedison/format', '@uniswap/conedison/provider'],
|
||||
moduleNameMapper: {
|
||||
'@uniswap/conedison/format': '@uniswap/conedison/dist/format',
|
||||
'@uniswap/conedison/provider': '@uniswap/conedison/dist/provider',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
const COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
|
||||
const PUDGY_COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
|
||||
const BONSAI_COLLECTION_ADDRESS = '0xec9c519d49856fd2f8133a0741b4dbe002ce211b'
|
||||
|
||||
describe('Testing nfts', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).first().click()
|
||||
cy.visit('/').then(() => {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).first().click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should load nft leaderboard', () => {
|
||||
@@ -16,7 +18,7 @@ describe('Testing nfts', () => {
|
||||
})
|
||||
|
||||
it('should load pudgy penguin collection page', () => {
|
||||
cy.visit(`/#/nfts/collection/${COLLECTION_ADDRESS}`)
|
||||
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
|
||||
cy.get(getTestSelector('nft-collection-asset')).should('exist')
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist')
|
||||
cy.get(getTestSelector('nft-filter')).first().click()
|
||||
@@ -24,13 +26,13 @@ describe('Testing nfts', () => {
|
||||
})
|
||||
|
||||
it('should be able to navigate to activity', () => {
|
||||
cy.visit(`/#/nfts/collection/${COLLECTION_ADDRESS}`)
|
||||
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
|
||||
cy.get(getTestSelector('nft-activity')).first().click()
|
||||
cy.get(getTestSelector('nft-activity-row')).should('exist')
|
||||
})
|
||||
|
||||
it('should go to the details page', () => {
|
||||
cy.visit(`/#/nfts/collection/${COLLECTION_ADDRESS}`)
|
||||
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
|
||||
cy.get(getTestSelector('nft-filter')).first().click()
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
|
||||
cy.get(getTestSelector('nft-details-link')).first().click()
|
||||
@@ -41,7 +43,7 @@ describe('Testing nfts', () => {
|
||||
})
|
||||
|
||||
it('should toggle buy now on details page', () => {
|
||||
cy.visit(`#/nfts/asset/${COLLECTION_ADDRESS}/2043`)
|
||||
cy.visit(`#/nfts/asset/${BONSAI_COLLECTION_ADDRESS}/7580`)
|
||||
cy.get(getTestSelector('nft-details-description-text')).should('exist')
|
||||
cy.get(getTestSelector('nft-details-description')).click()
|
||||
cy.get(getTestSelector('nft-details-description-text')).should('not.exist')
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Pool', () => {
|
||||
beforeEach(() => cy.visit('/pool'))
|
||||
beforeEach(() => {
|
||||
cy.visit('/pool').then(() => {
|
||||
cy.wait('@eth_blockNumber')
|
||||
})
|
||||
})
|
||||
|
||||
it('add liquidity links to /add/ETH', () => {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).first().click()
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
cy.get('body')
|
||||
.then((body) => {
|
||||
if (body.find(getTestSelector('FiatOnrampAnnouncement-close')).length > 0) {
|
||||
cy.get(getTestSelector('FiatOnrampAnnouncement-close')).click()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
cy.get('#join-pool-button')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
83
cypress/e2e/token-explore-filter.test.ts
Normal file
83
cypress/e2e/token-explore-filter.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
describe.skip('Token explore filter', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should filter correctly by uni search term', () => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByUni = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('uni'))
|
||||
cy.wrap(filteredByUni).as('filteredByUni')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('uni')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByUni').then((filteredByUni) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByUni.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByUni).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should filter correctly by dao search term', () => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByDao = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('dao'))
|
||||
cy.wrap(filteredByDao).as('filteredByDao')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('dao')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByDao').then((filteredByDao) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByDao.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByDao).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should filter correctly by ax search term', () => {
|
||||
cy.visit('/tokens')
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
const filteredByAx = tokenNames.filter((tokenName) => tokenName.toLowerCase().includes('ax'))
|
||||
cy.wrap(filteredByAx).as('filteredByAx')
|
||||
})
|
||||
|
||||
cy.get('[data-cy="explore-tokens-search-input"]')
|
||||
.clear()
|
||||
.type('ax')
|
||||
.type('{enter}')
|
||||
.then(() => {
|
||||
cy.get('[data-cy="token-name"]').its('length').should('be.lt', 100)
|
||||
cy.get('@filteredByAx').then((filteredByAx) => {
|
||||
cy.get('[data-cy="token-name"]').then(($els) => {
|
||||
const tokenNames = Array.from($els, (el) => el.innerText)
|
||||
expect(tokenNames.length).to.equal(filteredByAx.length)
|
||||
tokenNames.forEach((tokenName) => {
|
||||
expect(filteredByAx).to.include(tokenName)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
64
cypress/e2e/universal-search.test.ts
Normal file
64
cypress/e2e/universal-search.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Universal search bar', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="magnifying-icon"]')
|
||||
.parent()
|
||||
.then(($navIcon) => {
|
||||
$navIcon.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should yield clickable result for regular token or nft collection search term', () => {
|
||||
// Search for uni token by name.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear().type('uni')
|
||||
cy.get('[data-cy="searchbar-token-row-UNI"]')
|
||||
.should('contain.text', 'Uniswap')
|
||||
.and('contain.text', 'UNI')
|
||||
.and('contain.text', '$')
|
||||
.and('contain.text', '%')
|
||||
cy.get('[data-cy="searchbar-token-row-UNI"]').click()
|
||||
|
||||
cy.get('div').contains('Uniswap').should('exist')
|
||||
// Stats should have: TVL, 24H Volume, 52W low, 52W high.
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
cy.get('[data-cy="tvl"]').should('include.text', '$')
|
||||
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-low"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-high"]').should('include.text', '$')
|
||||
})
|
||||
|
||||
// About section should have description of token.
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.contains('UNI is the governance token for Uniswap').should('exist')
|
||||
})
|
||||
|
||||
it('should show recent tokens and popular tokens with empty search term', () => {
|
||||
cy.get('[data-cy="magnifying-icon"]')
|
||||
.parent()
|
||||
.then(($navIcon) => {
|
||||
$navIcon.click()
|
||||
})
|
||||
// Recently searched UNI token should exist.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear()
|
||||
cy.get('[data-cy="searchbar-dropdown"]')
|
||||
.contains('[data-cy="searchbar-dropdown"]', 'Recent searches')
|
||||
.find('[data-cy="searchbar-token-row-UNI"]')
|
||||
.should('exist')
|
||||
|
||||
// Most popular 3 tokens should be shown.
|
||||
cy.get('[data-cy="searchbar-dropdown"]')
|
||||
.contains('[data-cy="searchbar-dropdown"]', 'Popular tokens')
|
||||
.find('[data-cy^="searchbar-token-row"]')
|
||||
.its('length')
|
||||
.should('be.eq', 3)
|
||||
})
|
||||
|
||||
it.skip('should show blocked badge when blocked token is searched for', () => {
|
||||
// Search for mTSLA, which is a blocked token.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear().type('mtsla')
|
||||
cy.get('[data-cy="searchbar-token-row-mTSLA"]').find('[data-cy="blocked-icon"]').should('exist')
|
||||
})
|
||||
})
|
||||
@@ -13,8 +13,8 @@ describe('Wallet Dropdown', () => {
|
||||
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
@@ -36,8 +36,8 @@ describe('Wallet Dropdown', () => {
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
|
||||
@@ -70,6 +70,7 @@ beforeEach(() => {
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept(/infura.io/, (res) => {
|
||||
res.headers['origin'] = 'http://localhost:3000'
|
||||
res.alias = res.body.method
|
||||
res.continue()
|
||||
})
|
||||
|
||||
|
||||
21
package.json
21
package.json
@@ -111,7 +111,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.2",
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
"@coinbase/wallet-sdk": "^3.6.4",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@graphql-codegen/cli": "^2.15.0",
|
||||
@@ -133,9 +133,9 @@
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@sentry/react": "^7.29.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.2.0",
|
||||
"@uniswap/analytics-events": "^2.1.0",
|
||||
"@uniswap/conedison": "^1.1.1",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.4.0",
|
||||
"@uniswap/conedison": "^1.3.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
@@ -145,14 +145,14 @@
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/universal-router-sdk": "1.3.0",
|
||||
"@uniswap/universal-router-sdk": "^1.3.6",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "2.25.1",
|
||||
"@uniswap/widgets": "^2.40.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -165,16 +165,16 @@
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "^1.8.0",
|
||||
"@web3-react/coinbase-wallet": "8.0.34-beta.0",
|
||||
"@web3-react/coinbase-wallet": "8.0.35-beta.0",
|
||||
"@web3-react/core": "8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "8.0.26-beta.0",
|
||||
"@web3-react/eip1193": "8.0.27-beta.0",
|
||||
"@web3-react/empty": "8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.0.7-beta.0",
|
||||
"@web3-react/metamask": "8.0.28-beta.0",
|
||||
"@web3-react/metamask": "8.0.30-beta.0",
|
||||
"@web3-react/network": "8.0.27-beta.0",
|
||||
"@web3-react/types": "8.0.20-beta.0",
|
||||
"@web3-react/url": "8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.36-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.37-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"cids": "^1.0.0",
|
||||
@@ -222,6 +222,7 @@
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"statsig-react": "^1.22.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ExternalLink } from 'theme'
|
||||
import { BREAKPOINTS, ExternalLink, StyledRouterLink } from 'theme'
|
||||
|
||||
import { DiscordIcon, GithubIcon, TwitterIcon } from './Icons'
|
||||
import darkUnicornImgSrc from './images/unicornEmbossDark.png'
|
||||
@@ -97,14 +96,10 @@ const ExternalTextLink = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const TextLink = styled(Link)`
|
||||
const TextLink = styled(StyledRouterLink)`
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`
|
||||
|
||||
const Copyright = styled.span`
|
||||
@@ -120,7 +115,7 @@ const LogoSectionContent = () => {
|
||||
<>
|
||||
<StyledLogo src={isDarkMode ? darkUnicornImgSrc : lightUnicornImgSrc} alt="Uniswap Logo" />
|
||||
<SocialLinks>
|
||||
<SocialLink href="https://github.com/Uniswap" target="_blank" rel="noopener noreferrer">
|
||||
<SocialLink href="https://discord.gg/FCfyBSbCU5" target="_blank" rel="noopener noreferrer">
|
||||
<DiscordIcon size={32} />
|
||||
</SocialLink>
|
||||
<TraceEvent
|
||||
@@ -132,7 +127,7 @@ const LogoSectionContent = () => {
|
||||
<TwitterIcon size={32} />
|
||||
</SocialLink>
|
||||
</TraceEvent>
|
||||
<SocialLink href="https://discord.gg/FCfyBSbCU5" target="_blank" rel="noopener noreferrer">
|
||||
<SocialLink href="https://github.com/Uniswap" target="_blank" rel="noopener noreferrer">
|
||||
<GithubIcon size={32} />
|
||||
</SocialLink>
|
||||
</SocialLinks>
|
||||
@@ -158,15 +153,9 @@ export const AboutFooter = () => {
|
||||
</LinkGroup>
|
||||
<LinkGroup>
|
||||
<LinkGroupTitle>Protocol</LinkGroupTitle>
|
||||
<ExternalTextLink href="https://uniswap.org/community" target="_blank" rel="noopener noreferrer">
|
||||
Community
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/governance" target="_blank" rel="noopener noreferrer">
|
||||
Governance
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/developers" target="_blank" rel="noopener noreferrer">
|
||||
Developers
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/community">Community</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/governance">Governance</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/developers">Developers</ExternalTextLink>
|
||||
</LinkGroup>
|
||||
<LinkGroup>
|
||||
<LinkGroupTitle>Company</LinkGroupTitle>
|
||||
@@ -175,18 +164,14 @@ export const AboutFooter = () => {
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.CAREERS_LINK}
|
||||
>
|
||||
<ExternalTextLink href="https://boards.greenhouse.io/uniswaplabs" target="_blank" rel="noopener noreferrer">
|
||||
Careers
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://boards.greenhouse.io/uniswaplabs">Careers</ExternalTextLink>
|
||||
</TraceEvent>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.BLOG_LINK}
|
||||
>
|
||||
<ExternalTextLink href="https://uniswap.org/blog" target="_blank" rel="noopener noreferrer">
|
||||
Blog
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://uniswap.org/blog">Blog</ExternalTextLink>
|
||||
</TraceEvent>
|
||||
</LinkGroup>
|
||||
<LinkGroup>
|
||||
@@ -209,9 +194,7 @@ export const AboutFooter = () => {
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.SUPPORT_LINK}
|
||||
>
|
||||
<ExternalTextLink href="https://support.uniswap.org/hc/en-us" target="_blank" rel="noopener noreferrer">
|
||||
Help Center
|
||||
</ExternalTextLink>
|
||||
<ExternalTextLink href="https://support.uniswap.org/hc/en-us">Help Center</ExternalTextLink>
|
||||
</TraceEvent>
|
||||
</LinkGroup>
|
||||
</FooterLinks>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import {
|
||||
getConnection,
|
||||
getConnectionName,
|
||||
getHasCoinbaseExtensionInstalled,
|
||||
getHasMetaMaskExtensionInstalled,
|
||||
} from 'connection/utils'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMaskWallet } from 'connection/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
@@ -215,8 +210,8 @@ export default function AccountDetails({
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const hasMetaMaskExtension = getHasMetaMaskExtensionInstalled()
|
||||
const hasCoinbaseExtension = getHasCoinbaseExtensionInstalled()
|
||||
const hasMetaMaskExtension = getIsMetaMaskWallet()
|
||||
const hasCoinbaseExtension = getIsCoinbaseWallet()
|
||||
const isInjectedMobileBrowser = (hasMetaMaskExtension || hasCoinbaseExtension) && isMobile
|
||||
|
||||
function formatConnectorName() {
|
||||
|
||||
@@ -97,7 +97,8 @@ export const ButtonPrimary = styled(BaseButton)`
|
||||
export const SmallButtonPrimary = styled(ButtonPrimary)`
|
||||
width: auto;
|
||||
font-size: 16px;
|
||||
padding: 10px 16px;
|
||||
padding: ${({ padding }) => padding ?? '8px 12px'};
|
||||
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ interface DoubleCurrencyLogoProps {
|
||||
currency1?: Currency
|
||||
}
|
||||
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
const HigherLogoWrapper = styled.div`
|
||||
z-index: 1;
|
||||
`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
const CoveredLogoWapper = styled.div<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important;
|
||||
`
|
||||
@@ -33,8 +33,16 @@ export default function DoubleCurrencyLogo({
|
||||
}: DoubleCurrencyLogoProps) {
|
||||
return (
|
||||
<Wrapper sizeraw={size} margin={margin}>
|
||||
{currency0 && <HigherLogo currency={currency0} size={size.toString() + 'px'} />}
|
||||
{currency1 && <CoveredLogo currency={currency1} size={size.toString() + 'px'} sizeraw={size} />}
|
||||
{currency0 && (
|
||||
<HigherLogoWrapper>
|
||||
<CurrencyLogo hideL2Icon currency={currency0} size={size.toString() + 'px'} />
|
||||
</HigherLogoWrapper>
|
||||
)}
|
||||
{currency1 && (
|
||||
<CoveredLogoWapper sizeraw={size}>
|
||||
<CurrencyLogo hideL2Icon currency={currency1} size={size.toString() + 'px'} />
|
||||
</CoveredLogoWapper>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonLight, SmallButtonPrimary } from 'components/Button'
|
||||
import { ChevronUpIcon } from 'nft/components/icons'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import React, { PropsWithChildren, useState } from 'react'
|
||||
import { Copy } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isSentryEnabled } from 'utils/env'
|
||||
|
||||
@@ -217,13 +220,19 @@ const updateServiceWorkerInBackground = async () => {
|
||||
}
|
||||
|
||||
export default function ErrorBoundary({ children }: PropsWithChildren): JSX.Element {
|
||||
const { pathname } = useLocation()
|
||||
return (
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, eventId }) => <Fallback error={error} eventId={eventId} />}
|
||||
beforeCapture={(scope) => {
|
||||
scope.setLevel('fatal')
|
||||
}}
|
||||
onError={updateServiceWorkerInBackground}
|
||||
onError={(error) => {
|
||||
updateServiceWorkerInBackground()
|
||||
if (pathname === '/swap') {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_ERROR, { error })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Sentry.ErrorBoundary>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -211,24 +212,30 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.permit2}
|
||||
label="Permit 2 / Universal Router"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFiatOnrampFlag()}
|
||||
featureFlag={FeatureFlag.fiatOnramp}
|
||||
label="Fiat on-ramp"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={NftListV2Variant}
|
||||
value={useNftListV2Flag()}
|
||||
featureFlag={FeatureFlag.nftListV2}
|
||||
label="NFT Listing Page v2"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={PayWithAnyTokenVariant}
|
||||
value={usePayWithAnyTokenFlag()}
|
||||
featureFlag={FeatureFlag.payWithAnyToken}
|
||||
label="Pay With Any Token"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={SwapWidgetVariant}
|
||||
value={useSwapWidgetFlag()}
|
||||
featureFlag={FeatureFlag.swapWidget}
|
||||
label="Swap Widget"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={GqlRoutingVariant}
|
||||
value={useGqlRoutingFlag()}
|
||||
featureFlag={FeatureFlag.gqlRouting}
|
||||
label="GraphQL NFT Routing"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={NftGraphqlVariant}
|
||||
value={useNftGraphqlFlag()}
|
||||
featureFlag={FeatureFlag.nftGraphql}
|
||||
label="Migrate NFT read endpoints to GQL"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import fiatMaskUrl from 'assets/svg/fiat_mask.svg'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useToggleWalletDropdown } from 'state/application/hooks'
|
||||
@@ -120,13 +118,11 @@ export function FiatOnrampAnnouncement() {
|
||||
acknowledge({ user: true })
|
||||
}, [acknowledge, toggleWalletDropdown])
|
||||
|
||||
const fiatOnrampFlag = useFiatOnrampFlag()
|
||||
const openModal = useAppSelector((state) => state.application.openModal)
|
||||
|
||||
if (
|
||||
!account ||
|
||||
acks?.user ||
|
||||
fiatOnrampFlag === BaseVariant.Control ||
|
||||
localStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
|
||||
acks?.renderCount >= MAX_RENDER_COUNT ||
|
||||
isMobile ||
|
||||
|
||||
@@ -3,14 +3,17 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CustomLightSpinner, ThemedText } from 'theme'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import Modal from '../Modal'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
const MOONPAY_DARK_BACKGROUND = '#1c1c1e'
|
||||
const Wrapper = styled.div<{ isDarkMode: boolean }>`
|
||||
// #1c1c1e is the background color for the darkmode moonpay iframe as of 2/16/2023
|
||||
background-color: ${({ isDarkMode, theme }) => (isDarkMode ? MOONPAY_DARK_BACKGROUND : theme.white)};
|
||||
border-radius: 20px;
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
display: flex;
|
||||
@@ -28,8 +31,9 @@ const ErrorText = styled(ThemedText.BodyPrimary)`
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
`
|
||||
const StyledIframe = styled.iframe`
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
const StyledIframe = styled.iframe<{ isDarkMode: boolean }>`
|
||||
// #1c1c1e is the background color for the darkmode moonpay iframe as of 2/16/2023
|
||||
background-color: ${({ isDarkMode, theme }) => (isDarkMode ? MOONPAY_DARK_BACKGROUND : theme.white)};
|
||||
border-radius: 12px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
@@ -67,6 +71,7 @@ const MOONPAY_SUPPORTED_CURRENCY_CODES = [
|
||||
export default function FiatOnrampModal() {
|
||||
const { account } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const closeModal = useCloseModal()
|
||||
const fiatOnrampModalOpen = useModalIsOpen(ApplicationModal.FIAT_ONRAMP)
|
||||
|
||||
@@ -90,6 +95,7 @@ export default function FiatOnrampModal() {
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
theme: isDarkMode ? 'dark' : 'light',
|
||||
colorCode: theme.accentAction,
|
||||
defaultCurrencyCode: 'eth',
|
||||
redirectUrl: 'https://app.uniswap.org/#/swap',
|
||||
@@ -112,7 +118,7 @@ export default function FiatOnrampModal() {
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [account, theme.accentAction])
|
||||
}, [account, isDarkMode, theme.accentAction])
|
||||
|
||||
useEffect(() => {
|
||||
fetchSignedIframeUrl()
|
||||
@@ -120,7 +126,7 @@ export default function FiatOnrampModal() {
|
||||
|
||||
return (
|
||||
<Modal isOpen={fiatOnrampModalOpen} onDismiss={closeModal} maxHeight={720}>
|
||||
<Wrapper data-testid="fiat-onramp-modal">
|
||||
<Wrapper data-testid="fiat-onramp-modal" isDarkMode={isDarkMode}>
|
||||
{error ? (
|
||||
<>
|
||||
<ThemedText.MediumHeader>
|
||||
@@ -135,7 +141,12 @@ export default function FiatOnrampModal() {
|
||||
) : loading ? (
|
||||
<StyledSpinner src={Circle} alt="loading spinner" size="90px" />
|
||||
) : (
|
||||
<StyledIframe src={signedIframeUrl ?? ''} frameBorder="0" title="fiat-onramp-iframe" />
|
||||
<StyledIframe
|
||||
src={signedIframeUrl ?? ''}
|
||||
frameBorder="0"
|
||||
title="fiat-onramp-iframe"
|
||||
isDarkMode={isDarkMode}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import React from 'react'
|
||||
@@ -29,10 +30,28 @@ export type AssetLogoBaseProps = {
|
||||
backupImg?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
hideL2Icon?: boolean
|
||||
}
|
||||
type AssetLogoProps = AssetLogoBaseProps & { isNative?: boolean; address?: string | null; chainId?: number }
|
||||
|
||||
// TODO(cartcrom): add prop to optionally render an L2Icon w/ the logo
|
||||
const LogoContainer = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const L2NetworkLogo = styled.div<{ networkUrl?: string; parentSize: string }>`
|
||||
--size: ${({ parentSize }) => `calc(${parentSize} / 2)`};
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
background: url(${({ networkUrl }) => networkUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: ${({ parentSize }) => `calc(${parentSize} / 2) calc(${parentSize} / 2)`};
|
||||
display: ${({ networkUrl }) => !networkUrl && 'none'};
|
||||
`
|
||||
|
||||
/**
|
||||
* Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert
|
||||
*/
|
||||
@@ -44,25 +63,27 @@ export default function AssetLogo({
|
||||
backupImg,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
hideL2Icon = false,
|
||||
}: AssetLogoProps) {
|
||||
const imageProps = {
|
||||
alt: `${symbol ?? 'token'} logo`,
|
||||
size,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
const [src, nextSrc] = useTokenLogoSource(address, chainId, isNative, backupImg)
|
||||
const L2Icon = getChainInfo(chainId)?.circleLogoUrl
|
||||
|
||||
if (src) {
|
||||
return <LogoImage {...imageProps} src={src} onError={nextSrc} />
|
||||
} else {
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<LogoContainer style={style}>
|
||||
{src ? (
|
||||
<LogoImage {...imageProps} src={src} onError={nextSrc} />
|
||||
) : (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)}
|
||||
{!hideL2Icon && <L2NetworkLogo networkUrl={L2Icon} parentSize={size} />}
|
||||
</LogoContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function CurrencyLogo(
|
||||
address={props.currency?.wrapped.address}
|
||||
symbol={props.symbol ?? props.currency?.symbol}
|
||||
backupImg={(props.currency as TokenInfo)?.logoURI}
|
||||
hideL2Icon={props.hideL2Icon ?? true}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
@@ -7,14 +9,14 @@ import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
export default function QueryTokenLogo(
|
||||
props: AssetLogoBaseProps & {
|
||||
token?: TopToken | TokenQueryData
|
||||
token?: TopToken | TokenQueryData | SearchToken
|
||||
}
|
||||
) {
|
||||
const chainId = props.token?.chain ? CHAIN_NAME_TO_CHAIN_ID[props.token?.chain] : undefined
|
||||
|
||||
return (
|
||||
<AssetLogo
|
||||
isNative={props.token?.address === NATIVE_CHAIN_ID}
|
||||
isNative={props.token?.standard === TokenStandard.Native || props.token?.address === NATIVE_CHAIN_ID}
|
||||
chainId={chainId}
|
||||
address={props.token?.address}
|
||||
symbol={props.token?.symbol}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import Modal from 'components/Modal'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import { useModalIsOpen, useToggleMetaMaskConnectionErrorModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
padding: 32px 32px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const ShortColumn = styled(AutoColumn)`
|
||||
margin-top: 10px;
|
||||
`
|
||||
|
||||
const InfoText = styled(Text)`
|
||||
padding: 0 12px 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const StyledButton = styled(ButtonPrimary)`
|
||||
margin-top: 24px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const WarningIcon = styled(AlertTriangle)`
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 28px;
|
||||
stroke-width: 1px;
|
||||
margin-right: 4px;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
`
|
||||
|
||||
const onReconnect = () => window.location.reload()
|
||||
|
||||
export default function MetaMaskConnectionErrorModal() {
|
||||
const modalOpen = useModalIsOpen(ApplicationModal.METAMASK_CONNECTION_ERROR)
|
||||
const toggleModal = useToggleMetaMaskConnectionErrorModal()
|
||||
|
||||
return (
|
||||
<Modal isOpen={modalOpen} onDismiss={toggleModal} minHeight={false} maxHeight={90}>
|
||||
<Wrapper>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<div />
|
||||
<CloseIcon onClick={toggleModal} />
|
||||
</RowBetween>
|
||||
<Container>
|
||||
<AutoColumn>
|
||||
<LogoContainer>
|
||||
<WarningIcon />
|
||||
</LogoContainer>
|
||||
</AutoColumn>
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
<ThemedText.HeadlineSmall marginBottom="8px">
|
||||
<Trans>Wallet disconnected</Trans>
|
||||
</ThemedText.HeadlineSmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Trans>A MetaMask error caused your wallet to disconnect. Reload the page to reconnect.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<StyledButton onClick={onReconnect}>
|
||||
<Trans>Reload</Trans>
|
||||
</StyledButton>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -166,6 +166,9 @@ export const MenuDropdown = () => {
|
||||
<SecondaryLinkedText href="https://docs.uniswap.org/">
|
||||
<Trans>Documentation</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://uniswap.canny.io/feature-requests">
|
||||
<Trans>Feedback</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText
|
||||
onClick={() => {
|
||||
toggleOpen()
|
||||
|
||||
102
src/components/NavBar/RecentlySearchedAssets.ts
Normal file
102
src/components/NavBar/RecentlySearchedAssets.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||
import { Chain, NftCollection, useRecentlySearchedAssetsQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { useAtom } from 'jotai'
|
||||
import { atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
|
||||
|
||||
type RecentlySearchedAsset = {
|
||||
isNft?: boolean
|
||||
address: string
|
||||
chain: Chain
|
||||
}
|
||||
|
||||
// Temporary measure used until backend supports addressing by "NATIVE"
|
||||
const NATIVE_QUERY_ADDRESS_INPUT = null as unknown as string
|
||||
function getQueryAddress(chain: Chain) {
|
||||
return getNativeTokenDBAddress(chain) ?? NATIVE_QUERY_ADDRESS_INPUT
|
||||
}
|
||||
|
||||
const recentlySearchedAssetsAtom = atomWithStorage<RecentlySearchedAsset[]>('recentlySearchedAssets', [])
|
||||
|
||||
export function useAddRecentlySearchedAsset() {
|
||||
const [searchHistory, updateSearchHistory] = useAtom(recentlySearchedAssetsAtom)
|
||||
|
||||
return useCallback(
|
||||
(asset: RecentlySearchedAsset) => {
|
||||
// Removes the new asset if it was already in the array
|
||||
const newHistory = searchHistory.filter(
|
||||
(oldAsset) => !(oldAsset.address === asset.address && oldAsset.chain === asset.chain)
|
||||
)
|
||||
newHistory.unshift(asset)
|
||||
updateSearchHistory(newHistory)
|
||||
},
|
||||
[searchHistory, updateSearchHistory]
|
||||
)
|
||||
}
|
||||
|
||||
export function useRecentlySearchedAssets() {
|
||||
const history = useAtomValue(recentlySearchedAssetsAtom)
|
||||
const shortenedHistory = useMemo(() => history.slice(0, 4), [history])
|
||||
|
||||
const { data: queryData, loading } = useRecentlySearchedAssetsQuery({
|
||||
variables: {
|
||||
collectionAddresses: shortenedHistory.filter((asset) => asset.isNft).map((asset) => asset.address),
|
||||
contracts: shortenedHistory
|
||||
.filter((asset) => !asset.isNft)
|
||||
.map((token) => ({
|
||||
address: token.address === NATIVE_CHAIN_ID ? getQueryAddress(token.chain) : token.address,
|
||||
chain: token.chain,
|
||||
})),
|
||||
},
|
||||
})
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (shortenedHistory.length === 0) return []
|
||||
else if (!queryData) return undefined
|
||||
// Collects both tokens and collections in a map, so they can later be returned in original order
|
||||
const resultsMap: { [key: string]: GenieCollection | SearchToken } = {}
|
||||
|
||||
const queryCollections = queryData?.nftCollections?.edges.map((edge) => edge.node as NonNullable<NftCollection>)
|
||||
const collections = queryCollections?.map(
|
||||
(queryCollection): GenieCollection => {
|
||||
return {
|
||||
address: queryCollection.nftContracts?.[0]?.address ?? '',
|
||||
isVerified: queryCollection?.isVerified,
|
||||
name: queryCollection?.name,
|
||||
stats: {
|
||||
floor_price: queryCollection?.markets?.[0]?.floorPrice?.value,
|
||||
total_supply: queryCollection?.numAssets,
|
||||
},
|
||||
imageUrl: queryCollection?.image?.url ?? '',
|
||||
}
|
||||
},
|
||||
[queryCollections]
|
||||
)
|
||||
collections?.forEach((collection) => (resultsMap[collection.address] = collection))
|
||||
queryData.tokens?.filter(Boolean).forEach((token) => {
|
||||
resultsMap[token.address ?? `NATIVE-${token.chain}`] = token
|
||||
})
|
||||
|
||||
const data: (SearchToken | GenieCollection)[] = []
|
||||
shortenedHistory.forEach((asset) => {
|
||||
if (asset.address === 'NATIVE') {
|
||||
// Handles special case where wMATIC data needs to be used for MATIC
|
||||
const native = nativeOnChain(CHAIN_NAME_TO_CHAIN_ID[asset.chain] ?? SupportedChainId.MAINNET)
|
||||
const queryAddress = getQueryAddress(asset.chain)?.toLowerCase() ?? `NATIVE-${asset.chain}`
|
||||
const result = resultsMap[queryAddress]
|
||||
if (result) data.push({ ...result, address: 'NATIVE', ...native })
|
||||
} else {
|
||||
const result = resultsMap[asset.address]
|
||||
if (result) data.push(result)
|
||||
}
|
||||
})
|
||||
return data
|
||||
}, [queryData, shortenedHistory])
|
||||
|
||||
return { data, loading }
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName, InterfaceSectionName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import { useSearchTokens } from 'graphql/data/SearchTokens'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -12,7 +14,6 @@ import { Row } from 'nft/components/Flex'
|
||||
import { magicalGradientOnHover } from 'nft/css/common.css'
|
||||
import { useIsMobile, useIsTablet } from 'nft/hooks'
|
||||
import { fetchSearchCollections } from 'nft/queries'
|
||||
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
@@ -64,16 +65,8 @@ export const SearchBar = () => {
|
||||
}
|
||||
)
|
||||
|
||||
const { data: tokens, isLoading: tokensAreLoading } = useQuery(
|
||||
['searchTokens', debouncedSearchValue],
|
||||
() => fetchSearchTokens(debouncedSearchValue),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!debouncedSearchValue.length,
|
||||
}
|
||||
)
|
||||
const { chainId } = useWeb3React()
|
||||
const { data: tokens, loading: tokensAreLoading } = useSearchTokens(debouncedSearchValue, chainId ?? 1)
|
||||
|
||||
const isNFTPage = useIsNftPage()
|
||||
|
||||
@@ -148,6 +141,7 @@ export const SearchBar = () => {
|
||||
return (
|
||||
<Trace section={InterfaceSectionName.NAVBAR_SEARCH}>
|
||||
<Box
|
||||
data-cy="search-bar"
|
||||
position={{ sm: 'fixed', md: 'absolute', xl: 'relative' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
@@ -187,6 +181,7 @@ export const SearchBar = () => {
|
||||
render={({ translation }) => (
|
||||
<Box
|
||||
as="input"
|
||||
data-cy="search-bar-input"
|
||||
placeholder={translation as string}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceSectionName, NavBarSearchTypes } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
|
||||
import { HistoryDuration, SafetyLevel } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import useTrendingTokens from 'graphql/data/TrendingTokens'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { subheadSmall } from 'nft/css/common.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { fetchTrendingCollections } from 'nft/queries'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { formatEthPrice } from 'nft/utils/currency'
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { ClockIcon, TrendingArrow } from '../../nft/components/icons'
|
||||
import { useRecentlySearchedAssets } from './RecentlySearchedAssets'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
|
||||
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
|
||||
return (suggestion as FungibleToken).decimals === undefined
|
||||
function isCollection(suggestion: GenieCollection | SearchToken | TrendingCollection) {
|
||||
return (suggestion as SearchToken).decimals === undefined
|
||||
}
|
||||
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
suggestions: (GenieCollection | SearchToken)[]
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
@@ -46,14 +51,14 @@ const SearchBarDropdownSection = ({
|
||||
eventProperties,
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Column gap="12" data-cy="searchbar-dropdown">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="gray300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
<Column gap="12">
|
||||
{suggestions.map((suggestion, index) =>
|
||||
isLoading ? (
|
||||
isLoading || !suggestion ? (
|
||||
<SkeletonRow key={index} />
|
||||
) : isCollection(suggestion) ? (
|
||||
<CollectionRow
|
||||
@@ -73,7 +78,7 @@ const SearchBarDropdownSection = ({
|
||||
) : (
|
||||
<TokenRow
|
||||
key={suggestion.address}
|
||||
token={suggestion as FungibleToken}
|
||||
token={suggestion as SearchToken}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
@@ -92,9 +97,13 @@ const SearchBarDropdownSection = ({
|
||||
)
|
||||
}
|
||||
|
||||
function isKnownToken(token: SearchToken) {
|
||||
return token.project?.safetyLevel == SafetyLevel.Verified || token.project?.safetyLevel == SafetyLevel.MediumWarning
|
||||
}
|
||||
|
||||
interface SearchBarDropdownProps {
|
||||
toggleOpen: () => void
|
||||
tokens: FungibleToken[]
|
||||
tokens: SearchToken[]
|
||||
collections: GenieCollection[]
|
||||
queryText: string
|
||||
hasInput: boolean
|
||||
@@ -110,10 +119,13 @@ export const SearchBarDropdown = ({
|
||||
isLoading,
|
||||
}: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const { history: searchHistory, updateItem: updateSearchHistory } = useSearchHistory()
|
||||
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
|
||||
|
||||
const { data: searchHistory } = useRecentlySearchedAssets()
|
||||
const shortenedHistory = useMemo(() => searchHistory?.slice(0, 2) ?? [...Array<SearchToken>(2)], [searchHistory])
|
||||
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = useIsNftPage()
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
|
||||
@@ -122,45 +134,32 @@ export const SearchBarDropdown = ({
|
||||
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
|
||||
)
|
||||
|
||||
const trendingCollections = useMemo(
|
||||
() =>
|
||||
trendingCollectionResults
|
||||
? trendingCollectionResults
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
floor_price: formatEthPrice(collection.floor?.toString()),
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
|
||||
[isNFTPage, trendingCollectionResults]
|
||||
)
|
||||
const { data: gqlData, loading } = useTrendingCollections(3, HistoryDuration.Day)
|
||||
|
||||
const { data: trendingTokenResults, isLoading: trendingTokensAreLoading } = useQuery(
|
||||
['trendingTokens'],
|
||||
() => fetchTrendingTokens(4),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
useEffect(() => {
|
||||
trendingTokenResults?.forEach(updateSearchHistory)
|
||||
}, [trendingTokenResults, updateSearchHistory])
|
||||
const trendingCollections = useMemo(() => {
|
||||
const gatedTrendingCollections = isNftGraphqlEnabled ? gqlData : trendingCollectionResults
|
||||
return gatedTrendingCollections && (!isNftGraphqlEnabled || !loading)
|
||||
? gatedTrendingCollections
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: isNftGraphqlEnabled ? collection.floor : formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
floor_price: isNftGraphqlEnabled ? collection.floor : formatEthPrice(collection.floor?.toString()),
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)]
|
||||
}, [gqlData, isNFTPage, isNftGraphqlEnabled, loading, trendingCollectionResults])
|
||||
|
||||
const { data: trendingTokenData } = useTrendingTokens(useWeb3React().chainId)
|
||||
|
||||
const trendingTokensLength = isTokenPage ? 3 : 2
|
||||
const trendingTokens = useMemo(
|
||||
() =>
|
||||
trendingTokenResults
|
||||
? trendingTokenResults.slice(0, trendingTokensLength)
|
||||
: [...Array<FungibleToken>(trendingTokensLength)],
|
||||
[trendingTokenResults, trendingTokensLength]
|
||||
() => trendingTokenData?.slice(0, trendingTokensLength) ?? [...Array<SearchToken>(trendingTokensLength)],
|
||||
[trendingTokenData, trendingTokensLength]
|
||||
)
|
||||
|
||||
const totalSuggestions = hasInput
|
||||
@@ -197,10 +196,9 @@ export const SearchBarDropdown = ({
|
||||
}, [toggleOpen, hoveredIndex, totalSuggestions])
|
||||
|
||||
const hasVerifiedCollection = collections.some((collection) => collection.isVerified)
|
||||
const hasVerifiedToken = tokens.some((token) => token.onDefaultList)
|
||||
const hasKnownToken = tokens.some(isKnownToken)
|
||||
const showCollectionsFirst =
|
||||
(isNFTPage && (hasVerifiedCollection || !hasVerifiedToken)) ||
|
||||
(!isNFTPage && !hasVerifiedToken && hasVerifiedCollection)
|
||||
(isNFTPage && (hasVerifiedCollection || !hasKnownToken)) || (!isNFTPage && !hasKnownToken && hasVerifiedCollection)
|
||||
|
||||
const trace = JSON.stringify(useTrace({ section: InterfaceSectionName.NAVBAR_SEARCH }))
|
||||
|
||||
@@ -277,6 +275,7 @@ export const SearchBarDropdown = ({
|
||||
}}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
isLoading={!searchHistory}
|
||||
/>
|
||||
)}
|
||||
{!isNFTPage && (
|
||||
@@ -292,7 +291,7 @@ export const SearchBarDropdown = ({
|
||||
}}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingTokensAreLoading}
|
||||
isLoading={!trendingTokenData}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && (
|
||||
@@ -323,7 +322,7 @@ export const SearchBarDropdown = ({
|
||||
trendingCollections,
|
||||
trendingCollectionsAreLoading,
|
||||
trendingTokens,
|
||||
trendingTokensAreLoading,
|
||||
trendingTokenData,
|
||||
hoveredIndex,
|
||||
toggleOpen,
|
||||
shortenedHistory,
|
||||
@@ -334,6 +333,7 @@ export const SearchBarDropdown = ({
|
||||
queryText,
|
||||
totalSuggestions,
|
||||
trace,
|
||||
searchHistory,
|
||||
])
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,44 +1,34 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { formatUSDPrice } from '@uniswap/conedison/format'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import AssetLogo from 'components/Logo/AssetLogo'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { checkSearchTokenWarning } from 'constants/tokenSafety'
|
||||
import { Chain, TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { getTokenDetailsURL } from 'graphql/data/util'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { vars } from 'nft/css/sprinkles.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { getDeltaArrow } from '../Tokens/TokenDetails/PriceChart'
|
||||
import { DeltaText, getDeltaArrow } from '../Tokens/TokenDetails/PriceChart'
|
||||
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
const StyledLogoContainer = styled(LogoContainer)`
|
||||
margin-right: 8px;
|
||||
`
|
||||
const PriceChangeContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const PriceChangeText = styled.span<{ isNegative: boolean }>`
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme, isNegative }) => (isNegative ? theme.accentFailure : theme.accentSuccess)};
|
||||
`
|
||||
|
||||
const ArrowCell = styled.span`
|
||||
padding-top: 5px;
|
||||
padding-right: 3px;
|
||||
@@ -63,16 +53,15 @@ export const CollectionRow = ({
|
||||
}: CollectionRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
|
||||
const addRecentlySearchedAsset = useAddRecentlySearchedAsset()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(collection)
|
||||
addRecentlySearchedAsset({ ...collection, isNft: true, chain: Chain.Ethereum })
|
||||
toggleOpen()
|
||||
sendAnalyticsEvent(InterfaceEventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
|
||||
}, [addToSearchHistory, collection, toggleOpen, eventProperties])
|
||||
}, [addRecentlySearchedAsset, collection, toggleOpen, eventProperties])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@@ -130,17 +119,8 @@ export const CollectionRow = ({
|
||||
)
|
||||
}
|
||||
|
||||
function useBridgedAddress(token: FungibleToken): [string | undefined, number | undefined, string | undefined] {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const bridgedAddress = connectedChainId ? token.extensions?.bridgeInfo?.[connectedChainId]?.tokenAddress : undefined
|
||||
if (bridgedAddress && connectedChainId) {
|
||||
return [bridgedAddress, connectedChainId, getChainInfo(connectedChainId)?.circleLogoUrl]
|
||||
}
|
||||
return [undefined, undefined, undefined]
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
token: FungibleToken
|
||||
token: SearchToken
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
@@ -149,19 +129,18 @@ interface TokenRowProps {
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
const addRecentlySearchedAsset = useAddRecentlySearchedAsset()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(token)
|
||||
const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address
|
||||
address && addRecentlySearchedAsset({ address, chain: token.chain })
|
||||
|
||||
toggleOpen()
|
||||
sendAnalyticsEvent(InterfaceEventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
|
||||
}, [addToSearchHistory, toggleOpen, token, eventProperties])
|
||||
}, [addRecentlySearchedAsset, token, toggleOpen, eventProperties])
|
||||
|
||||
const [bridgedAddress, bridgedChain, L2Icon] = useBridgedAddress(token)
|
||||
const tokenDetailsPath = getTokenDetailsURL(bridgedAddress ?? token.address, undefined, bridgedChain ?? token.chainId)
|
||||
const tokenDetailsPath = getTokenDetailsURL(token)
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@@ -177,10 +156,11 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
}
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
|
||||
|
||||
const arrow = getDeltaArrow(token.price24hChange, 18)
|
||||
const arrow = getDeltaArrow(token.market?.pricePercentChange?.value, 18)
|
||||
|
||||
return (
|
||||
<Link
|
||||
data-cy={`searchbar-token-row-${token.symbol}`}
|
||||
to={tokenDetailsPath}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
@@ -189,39 +169,37 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
<StyledLogoContainer>
|
||||
<AssetLogo
|
||||
isNative={token.address === NATIVE_CHAIN_ID}
|
||||
address={token.address}
|
||||
chainId={token.chainId}
|
||||
symbol={token.symbol}
|
||||
size="36px"
|
||||
backupImg={token.logoURI}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</StyledLogoContainer>
|
||||
<QueryTokenLogo
|
||||
token={token}
|
||||
symbol={token.symbol}
|
||||
size="36px"
|
||||
backupImg={token.project?.logoUrl}
|
||||
style={{ paddingRight: '8px' }}
|
||||
/>
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
<TokenSafetyIcon warning={checkWarning(token.address)} />
|
||||
<TokenSafetyIcon warning={checkSearchTokenWarning(token)} />
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{token.symbol}</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{token.priceUsd && (
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{formatUSDPrice(token.priceUsd)}</Box>
|
||||
</Row>
|
||||
)}
|
||||
{token.price24hChange && (
|
||||
<PriceChangeContainer>
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
<PriceChangeText isNegative={token.price24hChange < 0}>
|
||||
{Math.abs(token.price24hChange).toFixed(2)}%
|
||||
</PriceChangeText>
|
||||
</PriceChangeContainer>
|
||||
{!!token.market?.price?.value && (
|
||||
<>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
|
||||
</Row>
|
||||
<PriceChangeContainer>
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
<ThemedText.BodySmall>
|
||||
<DeltaText delta={token.market?.pricePercentChange?.value}>
|
||||
{Math.abs(token.market?.pricePercentChange?.value ?? 0).toFixed(2)}%
|
||||
</DeltaText>
|
||||
</ThemedText.BodySmall>
|
||||
</PriceChangeContainer>
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
@@ -57,8 +56,7 @@ export const PageTabs = () => {
|
||||
pathname.startsWith('/pool') ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
pathname.startsWith('/increase')
|
||||
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
@@ -83,7 +81,6 @@ export const PageTabs = () => {
|
||||
const Navbar = () => {
|
||||
const isNftPage = useIsNftPage()
|
||||
const sellPageState = useProfilePageState((state) => state.state)
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
@@ -125,7 +122,7 @@ const Navbar = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
{isNftPage && (!isNftListV2 || sellPageState !== ProfilePageStateType.LISTING) && <Bag />}
|
||||
{isNftPage && sellPageState !== ProfilePageStateType.LISTING && <Bag />}
|
||||
{!isNftPage && (
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSelector />
|
||||
|
||||
@@ -3,11 +3,9 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import useGasPrice from 'hooks/useGasPrice'
|
||||
import { useIsLandingPage } from 'hooks/useIsLandingPage'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import useMachineTimeMs from 'hooks/useMachineTime'
|
||||
import JSBI from 'jsbi'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
@@ -71,17 +69,6 @@ const StyledPollingDot = styled.div<{ warning: boolean }>`
|
||||
transition: 250ms ease background-color;
|
||||
`
|
||||
|
||||
const StyledGasDot = styled.div`
|
||||
background-color: ${({ theme }) => theme.textTertiary};
|
||||
border-radius: 50%;
|
||||
height: 4px;
|
||||
min-height: 4px;
|
||||
min-width: 4px;
|
||||
position: relative;
|
||||
transition: 250ms ease background-color;
|
||||
width: 4px;
|
||||
`
|
||||
|
||||
const rotate360 = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
@@ -123,9 +110,6 @@ export default function Polling() {
|
||||
const isNftPage = useIsNftPage()
|
||||
const isLandingPage = useIsLandingPage()
|
||||
|
||||
const ethGasPrice = useGasPrice()
|
||||
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
|
||||
|
||||
const waitMsBeforeWarning =
|
||||
(chainId ? getChainInfo(chainId)?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
|
||||
|
||||
@@ -163,25 +147,6 @@ export default function Polling() {
|
||||
return (
|
||||
<RowFixed>
|
||||
<StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
|
||||
<ExternalLink href="https://etherscan.io/gastracker">
|
||||
{!!priceGwei && (
|
||||
<RowFixed style={{ marginRight: '8px' }}>
|
||||
<ThemedText.DeprecatedMain fontSize="11px" mr="8px">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The current fast gas amount for sending a transaction on L1. Gas fees are paid in Ethereum's
|
||||
native currency Ether (ETH) and denominated in GWEI.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
{priceGwei.toString()} <Trans>gwei</Trans>
|
||||
</MouseoverTooltip>
|
||||
</ThemedText.DeprecatedMain>
|
||||
<StyledGasDot />
|
||||
</RowFixed>
|
||||
)}
|
||||
</ExternalLink>
|
||||
<StyledPollingBlockNumber breathe={isMounting} hovering={isHover} warning={warning}>
|
||||
<ExternalLink href={blockExternalLinkHref}>
|
||||
<MouseoverTooltip
|
||||
|
||||
@@ -99,9 +99,9 @@ export default function PositionList({
|
||||
</ToggleLabel>
|
||||
</ToggleWrap>
|
||||
</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
return <PositionListItem key={p.tokenId.toString()} positionDetails={p} />
|
||||
})}
|
||||
{positions.map((p) => (
|
||||
<PositionListItem key={p.tokenId.toString()} {...p} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
38
src/components/PositionListItem/PositionListItem.test.tsx
Normal file
38
src/components/PositionListItem/PositionListItem.test.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { render, screen } from 'test-utils'
|
||||
|
||||
import PositionListItem from '.'
|
||||
|
||||
jest.mock('hooks/Tokens', () => {
|
||||
const originalModule = jest.requireActual('hooks/Tokens')
|
||||
const uniSDK = jest.requireActual('@uniswap/sdk-core')
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
useToken: jest.fn(
|
||||
() =>
|
||||
new uniSDK.Token(
|
||||
1,
|
||||
'0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||
8,
|
||||
'https://www.example.com',
|
||||
'example.com coin'
|
||||
)
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
test('PositionListItem should not render when the name contains a url', () => {
|
||||
const positionDetails = {
|
||||
token0: '0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||
token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
tokenId: BigNumber.from(436148),
|
||||
fee: 100,
|
||||
liquidity: BigNumber.from('0x5c985aff8059be04'),
|
||||
tickLower: -800,
|
||||
tickUpper: 1600,
|
||||
}
|
||||
render(<PositionListItem {...positionDetails} />)
|
||||
screen.debug()
|
||||
expect(screen.queryByText('.com', { exact: false })).toBe(null)
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent, Price, Token } from '@uniswap/sdk-core'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
@@ -15,9 +16,9 @@ import { Link } from 'react-router-dom'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
import { HideSmall, MEDIA_WIDTHS, SmallOnly } from 'theme'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
import { hasURL } from 'utils/urlChecks'
|
||||
|
||||
import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
|
||||
|
||||
@@ -109,7 +110,13 @@ const DataText = styled.div`
|
||||
`
|
||||
|
||||
interface PositionListItemProps {
|
||||
positionDetails: PositionDetails
|
||||
token0: string
|
||||
token1: string
|
||||
tokenId: BigNumber
|
||||
fee: number
|
||||
liquidity: BigNumber
|
||||
tickLower: number
|
||||
tickUpper: number
|
||||
}
|
||||
|
||||
export function getPriceOrderingFromPositionForUI(position?: Position): {
|
||||
@@ -166,16 +173,15 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
|
||||
}
|
||||
}
|
||||
|
||||
export default function PositionListItem({ positionDetails }: PositionListItemProps) {
|
||||
const {
|
||||
token0: token0Address,
|
||||
token1: token1Address,
|
||||
fee: feeAmount,
|
||||
liquidity,
|
||||
tickLower,
|
||||
tickUpper,
|
||||
} = positionDetails
|
||||
|
||||
export default function PositionListItem({
|
||||
token0: token0Address,
|
||||
token1: token1Address,
|
||||
tokenId,
|
||||
fee: feeAmount,
|
||||
liquidity,
|
||||
tickLower,
|
||||
tickUpper,
|
||||
}: PositionListItemProps) {
|
||||
const token0 = useToken(token0Address)
|
||||
const token1 = useToken(token1Address)
|
||||
|
||||
@@ -203,10 +209,23 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
// check if price is within range
|
||||
const outOfRange: boolean = pool ? pool.tickCurrent < tickLower || pool.tickCurrent >= tickUpper : false
|
||||
|
||||
const positionSummaryLink = '/pool/' + positionDetails.tokenId
|
||||
const positionSummaryLink = '/pool/' + tokenId
|
||||
|
||||
const removed = liquidity?.eq(0)
|
||||
|
||||
const containsURL = useMemo(
|
||||
() =>
|
||||
[token0?.name, token0?.symbol, token1?.name, token1?.symbol].reduce(
|
||||
(acc, testString) => acc || Boolean(testString && hasURL(testString)),
|
||||
false
|
||||
),
|
||||
[token0?.name, token0?.symbol, token1?.name, token1?.symbol]
|
||||
)
|
||||
|
||||
if (containsURL) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkRow to={positionSummaryLink}>
|
||||
<RowBetween>
|
||||
|
||||
@@ -105,235 +105,240 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
}
|
||||
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
style="height: 168px; width: 100%;"
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
style="height: 168px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
class="c0 c1 c2 c3 token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="Dai Stablecoin"
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
class="c6 css-vurnku"
|
||||
title="Dai Stablecoin"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="USD//C"
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
USD//C
|
||||
DAI
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
class="c6 css-vurnku"
|
||||
title="USD//C"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
USD//C
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="Wrapped BTC"
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
Wrapped BTC
|
||||
USDC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
class="c6 css-vurnku"
|
||||
title="Wrapped BTC"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
class="c4"
|
||||
>
|
||||
WBTC
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -383,31 +388,36 @@ exports[`renders loading rows when isLoading is true 1`] = `
|
||||
}
|
||||
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
style="height: 560px; width: 100%;"
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
style="height: 560px; width: 100%;"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
20
src/components/SearchModal/CurrencyList/index.css.ts
Normal file
20
src/components/SearchModal/CurrencyList/index.css.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
|
||||
export const scrollbarStyle = style([
|
||||
{
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: `${themeVars.colors.backgroundOutline} transparent`,
|
||||
height: '100%',
|
||||
selectors: {
|
||||
'&::-webkit-scrollbar': {
|
||||
background: 'transparent',
|
||||
width: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: `${themeVars.colors.backgroundOutline}`,
|
||||
borderRadius: '8px',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -20,6 +20,7 @@ import CurrencyLogo from '../../Logo/CurrencyLogo'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
import * as styles from './index.css'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency.isToken ? currency.address : 'ETHER'
|
||||
@@ -288,21 +289,34 @@ export default function CurrencyList({
|
||||
return currencyKey(currency)
|
||||
}, [])
|
||||
|
||||
return isLoading ? (
|
||||
<FixedSizeList height={height} ref={fixedListRef as any} width="100%" itemData={[]} itemCount={10} itemSize={56}>
|
||||
{LoadingRow}
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
width="100%"
|
||||
itemData={itemData}
|
||||
itemCount={itemData.length}
|
||||
itemSize={56}
|
||||
itemKey={itemKey}
|
||||
>
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
return (
|
||||
<div style={{ paddingRight: '4px' }}>
|
||||
{isLoading ? (
|
||||
<FixedSizeList
|
||||
className={styles.scrollbarStyle}
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
width="100%"
|
||||
itemData={[]}
|
||||
itemCount={10}
|
||||
itemSize={56}
|
||||
>
|
||||
{LoadingRow}
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
<FixedSizeList
|
||||
className={styles.scrollbarStyle}
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
width="100%"
|
||||
itemData={itemData}
|
||||
itemCount={itemData.length}
|
||||
itemSize={56}
|
||||
itemKey={itemKey}
|
||||
>
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ import { PaddedColumn, SearchInput, Separator } from './styleds'
|
||||
const ContentWrapper = styled(Column)`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1 1;
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
`
|
||||
|
||||
interface CurrencySearchProps {
|
||||
@@ -45,6 +47,7 @@ interface CurrencySearchProps {
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
onlyShowCurrenciesWithBalance?: boolean
|
||||
}
|
||||
|
||||
export function CurrencySearch({
|
||||
@@ -56,6 +59,7 @@ export function CurrencySearch({
|
||||
disableNonToken,
|
||||
onDismiss,
|
||||
isOpen,
|
||||
onlyShowCurrenciesWithBalance,
|
||||
}: CurrencySearchProps) {
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
@@ -92,6 +96,10 @@ export function CurrencySearch({
|
||||
!balancesAreLoading
|
||||
? filteredTokens
|
||||
.filter((token) => {
|
||||
if (onlyShowCurrenciesWithBalance) {
|
||||
return balances[token.address]?.greaterThan(0)
|
||||
}
|
||||
|
||||
// If there is no query, filter out unselected user-added tokens with no balance.
|
||||
if (!debouncedQuery && token instanceof UserAddedToken) {
|
||||
if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true
|
||||
@@ -101,7 +109,15 @@ export function CurrencySearch({
|
||||
})
|
||||
.sort(tokenComparator.bind(null, balances))
|
||||
: [],
|
||||
[balances, balancesAreLoading, debouncedQuery, filteredTokens, otherSelectedCurrency, selectedCurrency]
|
||||
[
|
||||
balances,
|
||||
balancesAreLoading,
|
||||
debouncedQuery,
|
||||
filteredTokens,
|
||||
otherSelectedCurrency,
|
||||
selectedCurrency,
|
||||
onlyShowCurrenciesWithBalance,
|
||||
]
|
||||
)
|
||||
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
|
||||
|
||||
@@ -114,11 +130,23 @@ export function CurrencySearch({
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
|
||||
const tokens = filteredSortedTokens.filter((t) => !(t.equals(wrapped) || (disableNonToken && t.isNative)))
|
||||
const natives = (disableNonToken || native.equals(wrapped) ? [wrapped] : [native, wrapped]).filter(
|
||||
(n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1
|
||||
)
|
||||
const shouldShowWrapped =
|
||||
!onlyShowCurrenciesWithBalance || (!balancesAreLoading && balances[wrapped.address]?.greaterThan(0))
|
||||
const natives = (
|
||||
disableNonToken || native.equals(wrapped) ? [wrapped] : shouldShowWrapped ? [native, wrapped] : [native]
|
||||
).filter((n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1)
|
||||
|
||||
return [...natives, ...tokens]
|
||||
}, [debouncedQuery, filteredSortedTokens, wrapped, disableNonToken, native])
|
||||
}, [
|
||||
debouncedQuery,
|
||||
filteredSortedTokens,
|
||||
onlyShowCurrenciesWithBalance,
|
||||
balancesAreLoading,
|
||||
balances,
|
||||
wrapped,
|
||||
disableNonToken,
|
||||
native,
|
||||
])
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
@@ -168,7 +196,9 @@ export function CurrencySearch({
|
||||
|
||||
// if no results on main list, show option to expand into inactive
|
||||
const filteredInactiveTokens = useSearchInactiveTokenLists(
|
||||
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
|
||||
!onlyShowCurrenciesWithBalance && (filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch))
|
||||
? debouncedQuery
|
||||
: undefined
|
||||
)
|
||||
|
||||
// Timeout token loader after 3 seconds to avoid hanging in a loading state.
|
||||
|
||||
@@ -17,6 +17,7 @@ interface CurrencySearchModalProps {
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
onlyShowCurrenciesWithBalance?: boolean
|
||||
}
|
||||
|
||||
enum CurrencyModalView {
|
||||
@@ -34,6 +35,7 @@ export default memo(function CurrencySearchModal({
|
||||
showCommonBases = false,
|
||||
showCurrencyAmount = true,
|
||||
disableNonToken = false,
|
||||
onlyShowCurrenciesWithBalance = false,
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.search)
|
||||
const lastOpen = useLast(isOpen)
|
||||
@@ -84,6 +86,7 @@ export default memo(function CurrencySearchModal({
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
onlyShowCurrenciesWithBalance={onlyShowCurrenciesWithBalance}
|
||||
/>
|
||||
)
|
||||
break
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function TokenSafetyIcon({ warning }: { warning: Warning | null }
|
||||
case WARNING_LEVEL.BLOCKED:
|
||||
return (
|
||||
<WarningContainer>
|
||||
<BlockedIcon strokeWidth={2.5} />
|
||||
<BlockedIcon data-cy="blocked-icon" strokeWidth={2.5} />
|
||||
</WarningContainer>
|
||||
)
|
||||
case WARNING_LEVEL.UNKNOWN:
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function BalanceSummary({ token }: { token: Currency }) {
|
||||
<Trans>Your balance on {label}</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<BalanceRow>
|
||||
<CurrencyLogo currency={token} size="2rem" />
|
||||
<CurrencyLogo currency={token} size="2rem" hideL2Icon={false} />
|
||||
<BalanceContainer>
|
||||
<BalanceAmountsContainer>
|
||||
<BalanceItem>
|
||||
|
||||
@@ -13,7 +13,7 @@ import TimePeriodSelector from './TimeSelector'
|
||||
function usePriceHistory(tokenPriceData: TokenPriceQuery): PricePoint[] | undefined {
|
||||
// Appends the current price to the end of the priceHistory array
|
||||
const priceHistory = useMemo(() => {
|
||||
const market = tokenPriceData.tokens?.[0]?.market
|
||||
const market = tokenPriceData.token?.market
|
||||
const priceHistory = market?.priceHistory?.filter(isPricePoint)
|
||||
const currentPrice = market?.price?.value
|
||||
if (Array.isArray(priceHistory) && currentPrice !== undefined) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { useDummyGateEnabled } from 'featureFlags/flags/dummyFeatureGate'
|
||||
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
@@ -13,9 +14,10 @@ const Wrapper = styled.div`
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-bottom: none;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px 20px 0px 0px;
|
||||
bottom: 56px;
|
||||
bottom: 52px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -86,6 +88,7 @@ export default function MobileBalanceSummaryFooter({ token }: { token: Currency
|
||||
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx)
|
||||
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats)
|
||||
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
|
||||
const isDummyGateFlagEnabled = useDummyGateEnabled()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
@@ -101,7 +104,7 @@ export default function MobileBalanceSummaryFooter({ token }: { token: Currency
|
||||
</BalanceInfo>
|
||||
)}
|
||||
<SwapButton to={`/swap?chainName=${chain}&outputCurrency=${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
|
||||
<Trans>Swap</Trans>
|
||||
<Trans>{isDummyGateFlagEnabled ? 'Go to Swap' : 'Swap'}</Trans>
|
||||
</SwapButton>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { WidgetSkeleton } from 'components/Widget'
|
||||
import { WIDGET_WIDTH } from 'components/Widget'
|
||||
import { DEFAULT_WIDGET_WIDTH } from 'components/Widget'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { textFadeIn } from 'theme/styles'
|
||||
|
||||
import { LoadingBubble } from '../loading'
|
||||
import { LogoContainer } from '../TokenTable/TokenRow'
|
||||
import { AboutContainer, AboutHeader } from './About'
|
||||
import { BreadcrumbNavLink } from './BreadcrumbNavLink'
|
||||
import { TokenPrice } from './PriceChart'
|
||||
@@ -44,7 +43,7 @@ export const RightPanel = styled.div`
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
width: ${WIDGET_WIDTH}px;
|
||||
width: ${DEFAULT_WIDGET_WIDTH}px;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
|
||||
display: flex;
|
||||
@@ -227,9 +226,7 @@ export default function TokenDetailsSkeleton() {
|
||||
</BreadcrumbNavLink>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<LogoContainer>
|
||||
<TokenLogoBubble />
|
||||
</LogoContainer>
|
||||
<TokenLogoBubble />
|
||||
<TitleBubble />
|
||||
</TokenNameCell>
|
||||
</TokenInfoContainer>
|
||||
|
||||
@@ -20,7 +20,6 @@ import TokenDetailsSkeleton, {
|
||||
TokenNameCell,
|
||||
} from 'components/Tokens/TokenDetails/Skeleton'
|
||||
import StatsSection from 'components/Tokens/TokenDetails/StatsSection'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import Widget from 'components/Widget'
|
||||
@@ -111,7 +110,7 @@ export default function TokenDetails({
|
||||
|
||||
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||
|
||||
const tokenQueryData = tokenQuery.tokens?.[0]
|
||||
const tokenQueryData = tokenQuery.token
|
||||
const crossChainMap = useMemo(
|
||||
() =>
|
||||
tokenQueryData?.project?.tokens.reduce((map, current) => {
|
||||
@@ -134,25 +133,27 @@ export default function TokenDetails({
|
||||
if (!address) return
|
||||
const bridgedAddress = crossChainMap[update]
|
||||
if (bridgedAddress) {
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(bridgedAddress, update)))
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL({ address: bridgedAddress, chain })))
|
||||
} else if (didFetchFromChain || token?.isNative) {
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(address, update)))
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain })))
|
||||
}
|
||||
},
|
||||
[address, crossChainMap, didFetchFromChain, navigate, token?.isNative]
|
||||
[address, chain, crossChainMap, didFetchFromChain, navigate, token?.isNative]
|
||||
)
|
||||
useOnGlobalChainSwitch(navigateToTokenForChain)
|
||||
|
||||
const navigateToWidgetSelectedToken = useCallback(
|
||||
(token: Currency) => {
|
||||
const address = token.isNative ? NATIVE_CHAIN_ID : token.address
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(address, chain)))
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain })))
|
||||
},
|
||||
[chain, navigate]
|
||||
)
|
||||
|
||||
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
|
||||
|
||||
const [openTokenSafetyModal, setOpenTokenSafetyModal] = useState(false)
|
||||
|
||||
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
|
||||
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(address, pageChainId) && tokenWarning !== null
|
||||
const onReviewSwapClick = useCallback(
|
||||
@@ -168,8 +169,6 @@ export default function TokenDetails({
|
||||
[continueSwap, setContinueSwap]
|
||||
)
|
||||
|
||||
const L2Icon = getChainInfo(pageChainId)?.circleLogoUrl
|
||||
|
||||
// address will never be undefined if token is defined; address is checked here to appease typechecker
|
||||
if (token === undefined || !address) {
|
||||
return <InvalidTokenDetails chainName={address && getChainInfo(pageChainId)?.label} />
|
||||
@@ -188,10 +187,8 @@ export default function TokenDetails({
|
||||
</BreadcrumbNavLink>
|
||||
<TokenInfoContainer data-testid="token-info-container">
|
||||
<TokenNameCell>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo currency={token} size="32px" />
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
<CurrencyLogo currency={token} size="32px" hideL2Icon={false} />
|
||||
|
||||
{token.name ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
</TokenNameCell>
|
||||
@@ -220,22 +217,28 @@ export default function TokenDetails({
|
||||
<TokenDetailsSkeleton />
|
||||
)}
|
||||
|
||||
<RightPanel>
|
||||
<Widget
|
||||
token={token ?? undefined}
|
||||
onTokenChange={navigateToWidgetSelectedToken}
|
||||
onReviewSwapClick={onReviewSwapClick}
|
||||
/>
|
||||
<RightPanel onClick={() => isBlockedToken && setOpenTokenSafetyModal(true)}>
|
||||
<div style={{ pointerEvents: isBlockedToken ? 'none' : 'auto' }}>
|
||||
<Widget
|
||||
defaultTokens={{
|
||||
default: token ?? undefined,
|
||||
}}
|
||||
onDefaultTokenChange={navigateToWidgetSelectedToken}
|
||||
onReviewSwapClick={onReviewSwapClick}
|
||||
/>
|
||||
</div>
|
||||
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
|
||||
{token && <BalanceSummary token={token} />}
|
||||
</RightPanel>
|
||||
{token && <MobileBalanceSummaryFooter token={token} />}
|
||||
|
||||
<TokenSafetyModal
|
||||
isOpen={isBlockedToken || !!continueSwap}
|
||||
isOpen={openTokenSafetyModal || !!continueSwap}
|
||||
tokenAddress={address}
|
||||
onContinue={() => onResolveSwap(true)}
|
||||
onBlocked={() => navigate(-1)}
|
||||
onBlocked={() => {
|
||||
setOpenTokenSafetyModal(false)
|
||||
}}
|
||||
onCancel={() => onResolveSwap(false)}
|
||||
showCancel={true}
|
||||
/>
|
||||
|
||||
@@ -84,6 +84,7 @@ export default function SearchBar() {
|
||||
element={InterfaceElementName.EXPLORE_SEARCH_INPUT}
|
||||
>
|
||||
<SearchInput
|
||||
data-cy="explore-tokens-search-input"
|
||||
type="search"
|
||||
placeholder={`${translation}`}
|
||||
id="searchBar"
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ParentSize } from '@visx/responsive'
|
||||
import SparklineChart from 'components/Charts/SparklineChart'
|
||||
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
@@ -279,23 +278,6 @@ export const SparkLineLoadingBubble = styled(LongLoadingBubble)`
|
||||
height: 4px;
|
||||
`
|
||||
|
||||
export const L2NetworkLogo = styled.div<{ networkUrl?: string; size?: string }>`
|
||||
height: ${({ size }) => size ?? '12px'};
|
||||
width: ${({ size }) => size ?? '12px'};
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
background: url(${({ networkUrl }) => networkUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: ${({ size }) => (size ? `${size} ${size}` : '12px 12px')};
|
||||
display: ${({ networkUrl }) => !networkUrl && 'none'};
|
||||
`
|
||||
export const LogoContainer = styled.div`
|
||||
position: relative;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const InfoIconContainer = styled.div`
|
||||
margin-left: 2px;
|
||||
display: flex;
|
||||
@@ -307,7 +289,9 @@ export const HEADER_DESCRIPTIONS: Record<TokenSortMethod, ReactNode | undefined>
|
||||
[TokenSortMethod.PRICE]: undefined,
|
||||
[TokenSortMethod.PERCENT_CHANGE]: undefined,
|
||||
[TokenSortMethod.TOTAL_VALUE_LOCKED]: (
|
||||
<Trans>Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool.</Trans>
|
||||
<Trans>
|
||||
Total value locked (TVL) is the aggregate amount of the asset available across all Uniswap v3 liquidity pools.
|
||||
</Trans>
|
||||
),
|
||||
[TokenSortMethod.VOLUME]: (
|
||||
<Trans>Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame.</Trans>
|
||||
@@ -442,18 +426,17 @@ interface LoadedRowProps {
|
||||
tokenListLength: number
|
||||
token: NonNullable<TopToken>
|
||||
sparklineMap: SparklineMap
|
||||
volumeRank: number
|
||||
sortRank: number
|
||||
}
|
||||
|
||||
/* Loaded State: row component with token information */
|
||||
export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const { tokenListIndex, tokenListLength, token, volumeRank } = props
|
||||
const { tokenListIndex, tokenListLength, token, sortRank } = props
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
|
||||
const lowercaseChainName = useParams<{ chainName?: string }>().chainName?.toUpperCase() ?? 'ethereum'
|
||||
const filterNetwork = lowercaseChainName.toUpperCase()
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
|
||||
const L2Icon = getChainInfo(chainId)?.circleLogoUrl
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
const delta = token.market?.pricePercentChange?.value
|
||||
const arrow = getDeltaArrow(delta)
|
||||
@@ -465,7 +448,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
token_address: token.address,
|
||||
token_symbol: token.symbol,
|
||||
token_list_index: tokenListIndex,
|
||||
token_list_rank: volumeRank,
|
||||
token_list_rank: sortRank,
|
||||
token_list_length: tokenListLength,
|
||||
time_frame: timePeriod,
|
||||
search_token_address_input: filterString,
|
||||
@@ -475,22 +458,19 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
return (
|
||||
<div ref={ref} data-testid={`token-table-row-${token.symbol}`}>
|
||||
<StyledLink
|
||||
to={getTokenDetailsURL(token.address ?? '', token.chain)}
|
||||
to={getTokenDetailsURL(token)}
|
||||
onClick={() =>
|
||||
sendAnalyticsEvent(InterfaceEventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)
|
||||
}
|
||||
>
|
||||
<TokenRow
|
||||
header={false}
|
||||
listNumber={volumeRank}
|
||||
listNumber={sortRank}
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<LogoContainer>
|
||||
<QueryTokenLogo token={token} />
|
||||
<L2NetworkLogo networkUrl={L2Icon} />
|
||||
</LogoContainer>
|
||||
<QueryTokenLogo token={token} />
|
||||
<TokenInfoCell>
|
||||
<TokenName>{token.name}</TokenName>
|
||||
<TokenName data-cy="token-name">{token.name}</TokenName>
|
||||
<TokenSymbol>{token.symbol}</TokenSymbol>
|
||||
</TokenInfoCell>
|
||||
</ClickableName>
|
||||
|
||||
@@ -76,12 +76,11 @@ function LoadingTokenTable({ rowCount = PAGE_SIZE }: { rowCount?: number }) {
|
||||
}
|
||||
|
||||
export default function TokenTable() {
|
||||
// TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s
|
||||
const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName)
|
||||
const { tokens, tokenVolumeRank, loadingTokens, sparklines } = useTopTokens(chainName)
|
||||
const { tokens, tokenSortRank, loadingTokens, sparklines } = useTopTokens(chainName)
|
||||
|
||||
/* loading and error state */
|
||||
if (loadingTokens) {
|
||||
if (loadingTokens && !tokens) {
|
||||
return <LoadingTokenTable rowCount={PAGE_SIZE} />
|
||||
} else if (!tokens) {
|
||||
return (
|
||||
@@ -110,7 +109,7 @@ export default function TokenTable() {
|
||||
tokenListLength={tokens.length}
|
||||
token={token}
|
||||
sparklineMap={sparklines}
|
||||
volumeRank={tokenVolumeRank[token.address]}
|
||||
sortRank={tokenSortRank[token.address]}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -2,8 +2,6 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { lazy } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
@@ -12,7 +10,6 @@ import { ApplicationModal } from 'state/application/reducer'
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||
const MetaMaskConnectionErrorModal = lazy(() => import('components/MetaMaskConnectionErrorModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
@@ -21,7 +18,6 @@ export default function TopLevelModals() {
|
||||
const { account } = useWeb3React()
|
||||
useAccountRiskCheck(account)
|
||||
const accountBlocked = Boolean(blockedAccountModalOpen && account)
|
||||
const fiatOnrampFlagEnabled = useFiatOnrampFlag() === BaseVariant.Enabled
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -30,8 +26,7 @@ export default function TopLevelModals() {
|
||||
<Bag />
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
<MetaMaskConnectionErrorModal />
|
||||
{fiatOnrampFlagEnabled && <FiatOnrampModal />}
|
||||
<FiatOnrampModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import Tooltip from 'components/Tooltip'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { BaseVariant } from 'featureFlags'
|
||||
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
@@ -126,15 +124,6 @@ const FiatOnrampAvailabilityExternalLink = styled(ExternalLink)`
|
||||
margin-left: 6px;
|
||||
width: 14px;
|
||||
`
|
||||
const FlexContainer = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const StatusWrapper = styled.div`
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
width: 70%;
|
||||
`
|
||||
|
||||
const TruncatedTextStyle = css`
|
||||
text-overflow: ellipsis;
|
||||
@@ -142,8 +131,14 @@ const TruncatedTextStyle = css`
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const AccountNamesWrapper = styled.div`
|
||||
const FlexContainer = styled.div`
|
||||
${TruncatedTextStyle}
|
||||
padding-right: 4px;
|
||||
display: inline-flex;
|
||||
`
|
||||
|
||||
const AccountNamesWrapper = styled.div`
|
||||
min-width: 0;
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
@@ -228,7 +223,6 @@ const AuthenticatedHeader = () => {
|
||||
closeModal()
|
||||
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
|
||||
|
||||
const fiatOnrampFlag = useFiatOnrampFlag()
|
||||
// animate the border of the buy crypto button when a user navigates here from the feature announcement
|
||||
// can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
|
||||
const [acknowledgements, acknowledge] = useFiatOnrampAck()
|
||||
@@ -280,19 +274,17 @@ const AuthenticatedHeader = () => {
|
||||
return (
|
||||
<>
|
||||
<HeaderWrapper>
|
||||
<StatusWrapper>
|
||||
<FlexContainer>
|
||||
<StatusIcon connectionType={connectionType} size={24} />
|
||||
{ENSName ? (
|
||||
<AccountNamesWrapper>
|
||||
<ENSNameContainer>{ENSName}</ENSNameContainer>
|
||||
<AccountContainer>{account && shortenAddress(account, 2, 4)}</AccountContainer>
|
||||
</AccountNamesWrapper>
|
||||
) : (
|
||||
<ThemedText.SubHeader marginTop="2.5px">{account && shortenAddress(account, 2, 4)}</ThemedText.SubHeader>
|
||||
)}
|
||||
</FlexContainer>
|
||||
</StatusWrapper>
|
||||
<FlexContainer>
|
||||
<StatusIcon connectionType={connectionType} size={24} />
|
||||
{ENSName ? (
|
||||
<AccountNamesWrapper>
|
||||
<ENSNameContainer>{ENSName}</ENSNameContainer>
|
||||
<AccountContainer>{account && shortenAddress(account, 2, 4)}</AccountContainer>
|
||||
</AccountNamesWrapper>
|
||||
) : (
|
||||
<ThemedText.SubHeader marginTop="2.5px">{account && shortenAddress(account, 2, 4)}</ThemedText.SubHeader>
|
||||
)}
|
||||
</FlexContainer>
|
||||
<IconContainer>
|
||||
<IconButton onClick={copy} Icon={Copy}>
|
||||
{isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>}
|
||||
@@ -321,47 +313,43 @@ const AuthenticatedHeader = () => {
|
||||
>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
{fiatOnrampFlag === BaseVariant.Enabled && (
|
||||
<>
|
||||
<BuyCryptoButton
|
||||
$animateBorder={animateBuyCryptoButtonBorder}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
disabled={disableBuyCryptoButton}
|
||||
>
|
||||
{error ? (
|
||||
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
|
||||
<BuyCryptoButton
|
||||
$animateBorder={animateBuyCryptoButtonBorder}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
disabled={disableBuyCryptoButton}
|
||||
>
|
||||
{error ? (
|
||||
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
|
||||
) : (
|
||||
<>
|
||||
{fiatOnrampAvailabilityLoading ? (
|
||||
<StyledLoadingButtonSpinner />
|
||||
) : (
|
||||
<>
|
||||
{fiatOnrampAvailabilityLoading ? (
|
||||
<StyledLoadingButtonSpinner />
|
||||
) : (
|
||||
<CreditCard height="20px" width="20px" />
|
||||
)}{' '}
|
||||
<Trans>Buy crypto</Trans>
|
||||
</>
|
||||
)}
|
||||
</BuyCryptoButton>
|
||||
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
|
||||
<FiatOnrampNotAvailableText marginTop="8px">
|
||||
<Trans>Not available in your region</Trans>
|
||||
<Tooltip
|
||||
show={showFiatOnrampUnavailableTooltip}
|
||||
text={<Trans>Moonpay is not available in some regions. Click to learn more.</Trans>}
|
||||
>
|
||||
<FiatOnrampAvailabilityExternalLink
|
||||
onMouseEnter={openFiatOnrampUnavailableTooltip}
|
||||
onMouseLeave={closeFiatOnrampUnavailableTooltip}
|
||||
style={{ color: 'inherit' }}
|
||||
href="https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-"
|
||||
>
|
||||
<StyledInfoIcon />
|
||||
</FiatOnrampAvailabilityExternalLink>
|
||||
</Tooltip>
|
||||
</FiatOnrampNotAvailableText>
|
||||
)}
|
||||
</>
|
||||
<CreditCard height="20px" width="20px" />
|
||||
)}{' '}
|
||||
<Trans>Buy crypto</Trans>
|
||||
</>
|
||||
)}
|
||||
</BuyCryptoButton>
|
||||
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
|
||||
<FiatOnrampNotAvailableText marginTop="8px">
|
||||
<Trans>Not available in your region</Trans>
|
||||
<Tooltip
|
||||
show={showFiatOnrampUnavailableTooltip}
|
||||
text={<Trans>Moonpay is not available in some regions. Click to learn more.</Trans>}
|
||||
>
|
||||
<FiatOnrampAvailabilityExternalLink
|
||||
onMouseEnter={openFiatOnrampUnavailableTooltip}
|
||||
onMouseLeave={closeFiatOnrampUnavailableTooltip}
|
||||
style={{ color: 'inherit' }}
|
||||
href="https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-"
|
||||
>
|
||||
<StyledInfoIcon />
|
||||
</FiatOnrampAvailabilityExternalLink>
|
||||
</Tooltip>
|
||||
</FiatOnrampNotAvailableText>
|
||||
)}
|
||||
{isUnclaimed && (
|
||||
<UNIButton onClick={openClaimModal} size={ButtonSize.medium} emphasis={ButtonEmphasis.medium}>
|
||||
|
||||
@@ -57,7 +57,7 @@ const StyledChevron = styled(ChevronLeft)`
|
||||
const BackSection = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: -webkit-fill-available;
|
||||
width: fill-available;
|
||||
margin: 0px 2vw 0px 0px;
|
||||
padding: 0px 0px 2vh 0px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
|
||||
@@ -22,9 +22,6 @@ jest.mock('.../../state/application/hooks', () => {
|
||||
useToggleWalletModal: () => {
|
||||
return
|
||||
},
|
||||
useToggleMetaMaskConnectionErrorModal: () => {
|
||||
return
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,8 +70,8 @@ it('loads Wallet Modal on desktop', async () => {
|
||||
|
||||
it('loads Wallet Modal on desktop with generic Injected', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Browser Wallet')).toBeInTheDocument()
|
||||
@@ -85,8 +82,8 @@ it('loads Wallet Modal on desktop with generic Injected', async () => {
|
||||
|
||||
it('loads Wallet Modal on desktop with MetaMask installed', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
@@ -99,8 +96,8 @@ it('loads Wallet Modal on mobile', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Open in Coinbase Wallet')).toBeInTheDocument()
|
||||
@@ -112,8 +109,8 @@ it('loads Wallet Modal on MetaMask browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
@@ -124,8 +121,8 @@ it('loads Wallet Modal on Coinbase Wallet browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(true)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
|
||||
@@ -10,9 +10,9 @@ import { networkConnection } from 'connection'
|
||||
import {
|
||||
getConnection,
|
||||
getConnectionName,
|
||||
getHasCoinbaseExtensionInstalled,
|
||||
getHasMetaMaskExtensionInstalled,
|
||||
getIsCoinbaseWallet,
|
||||
getIsInjected,
|
||||
getIsMetaMaskWallet,
|
||||
} from 'connection/utils'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
@@ -253,8 +253,8 @@ export default function WalletModal({
|
||||
|
||||
function getOptions() {
|
||||
const isInjected = getIsInjected()
|
||||
const hasMetaMaskExtension = getHasMetaMaskExtensionInstalled()
|
||||
const hasCoinbaseExtension = getHasCoinbaseExtensionInstalled()
|
||||
const hasMetaMaskExtension = getIsMetaMaskWallet()
|
||||
const hasCoinbaseExtension = getIsCoinbaseWallet()
|
||||
|
||||
const isCoinbaseWalletBrowser = isMobile && hasCoinbaseExtension
|
||||
const isMetaMaskBrowser = isMobile && hasMetaMaskExtension
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useWeb3React, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { Connection } from 'connection'
|
||||
import { setMetMaskErrorHandler } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
@@ -9,19 +8,8 @@ import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/tra
|
||||
import useEagerlyConnect from 'hooks/useEagerlyConnect'
|
||||
import useOrderedConnections from 'hooks/useOrderedConnections'
|
||||
import { ReactNode, useEffect, useMemo } from 'react'
|
||||
import { useToggleMetaMaskConnectionErrorModal } from 'state/application/hooks'
|
||||
|
||||
export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
// https://github.com/MetaMask/metamask-extension/issues/13375
|
||||
const toggleMetaMaskConnectionErrorModal = useToggleMetaMaskConnectionErrorModal()
|
||||
useEffect(() => {
|
||||
setMetMaskErrorHandler((error) => {
|
||||
if (error.code === 1013) {
|
||||
toggleMetaMaskConnectionErrorModal()
|
||||
}
|
||||
})
|
||||
}, [toggleMetaMaskConnectionErrorModal])
|
||||
|
||||
useEagerlyConnect()
|
||||
const connections = useOrderedConnections()
|
||||
const connectors: [Connector, Web3ReactHooks][] = connections.map(({ hooks, connector }) => [connector, hooks])
|
||||
|
||||
@@ -26,16 +26,17 @@ import {
|
||||
getTokenAddress,
|
||||
} from 'lib/utils/analytics'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useToggleWalletModal } from 'state/application/hooks'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
import { switchChain } from 'utils/switchChain'
|
||||
|
||||
import { useSyncWidgetInputs } from './inputs'
|
||||
import { DefaultTokens, useSyncWidgetInputs } from './inputs'
|
||||
import { useSyncWidgetSettings } from './settings'
|
||||
import { DARK_THEME, LIGHT_THEME } from './theme'
|
||||
import { useSyncWidgetTransactions } from './transactions'
|
||||
|
||||
export const WIDGET_WIDTH = 360
|
||||
export const DEFAULT_WIDGET_WIDTH = 360
|
||||
|
||||
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
|
||||
|
||||
@@ -44,19 +45,34 @@ function useWidgetTheme() {
|
||||
}
|
||||
|
||||
interface WidgetProps {
|
||||
token?: Currency
|
||||
onTokenChange?: (token: Currency) => void
|
||||
defaultTokens: DefaultTokens
|
||||
width?: number | string
|
||||
onDefaultTokenChange?: (token: Currency) => void
|
||||
onReviewSwapClick?: OnReviewSwapClick
|
||||
}
|
||||
|
||||
export default function Widget({ token, onTokenChange, onReviewSwapClick }: WidgetProps) {
|
||||
const { connector, provider } = useWeb3React()
|
||||
export default function Widget({
|
||||
defaultTokens,
|
||||
width = DEFAULT_WIDGET_WIDTH,
|
||||
onDefaultTokenChange,
|
||||
onReviewSwapClick,
|
||||
}: WidgetProps) {
|
||||
const { connector, provider, chainId } = useWeb3React()
|
||||
const locale = useActiveLocale()
|
||||
const theme = useWidgetTheme()
|
||||
const { inputs, tokenSelector } = useSyncWidgetInputs({ token, onTokenChange })
|
||||
const { inputs, tokenSelector } = useSyncWidgetInputs({
|
||||
defaultTokens,
|
||||
onDefaultTokenChange,
|
||||
})
|
||||
const { settings } = useSyncWidgetSettings()
|
||||
const { transactions } = useSyncWidgetTransactions()
|
||||
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const onConnectWalletClick = useCallback(() => {
|
||||
toggleWalletModal()
|
||||
return false // prevents the in-widget wallet modal from opening
|
||||
}, [toggleWalletModal])
|
||||
|
||||
const onSwitchChain = useCallback(
|
||||
// TODO(WEB-1757): Widget should not break if this rejects - upstream the catch to ignore it.
|
||||
({ chainId }: AddEthereumChainParameter) => switchChain(connector, Number(chainId)).catch(() => undefined),
|
||||
@@ -152,8 +168,9 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
|
||||
routerUrl={WIDGET_ROUTER_URL}
|
||||
locale={locale}
|
||||
theme={theme}
|
||||
width={WIDGET_WIDTH}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
width={width}
|
||||
defaultChainId={chainId}
|
||||
onConnectWalletClick={onConnectWalletClick}
|
||||
provider={provider}
|
||||
onSwitchChain={onSwitchChain}
|
||||
tokenList={EMPTY_TOKEN_LIST} // prevents loading the default token list, as we use our own token selector UI
|
||||
@@ -166,13 +183,16 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
|
||||
onSwapApprove={onApproveToken}
|
||||
onInitialSwapQuote={onInitialSwapQuote}
|
||||
onSwapPriceUpdateAck={onSwapPriceUpdateAck}
|
||||
onError={(error, errorInfo) => {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_ERROR, { error, errorInfo, ...trace })
|
||||
}}
|
||||
/>
|
||||
{tokenSelector}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function WidgetSkeleton() {
|
||||
export function WidgetSkeleton({ width = DEFAULT_WIDGET_WIDTH }: { width?: number | string }) {
|
||||
const theme = useWidgetTheme()
|
||||
return <SwapWidgetSkeleton theme={theme} width={WIDGET_WIDTH} />
|
||||
return <SwapWidgetSkeleton theme={theme} width={width} />
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceSectionName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, Field, SwapController, SwapEventHandlers, TradeType } from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
const EMPTY_AMOUNT = ''
|
||||
|
||||
type SwapValue = Required<SwapController>['value']
|
||||
type SwapTokens = Pick<SwapValue, Field.INPUT | Field.OUTPUT> & { default?: Currency }
|
||||
export type DefaultTokens = Partial<SwapTokens>
|
||||
|
||||
function includesDefaultToken(tokens: SwapTokens) {
|
||||
if (!tokens.default) return true
|
||||
return tokens[Field.INPUT]?.equals(tokens.default) || tokens[Field.OUTPUT]?.equals(tokens.default)
|
||||
function missingDefaultToken(tokens: SwapTokens) {
|
||||
if (!tokens.default) return false
|
||||
return !tokens[Field.INPUT]?.equals(tokens.default) && !tokens[Field.OUTPUT]?.equals(tokens.default)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,27 +23,42 @@ function includesDefaultToken(tokens: SwapTokens) {
|
||||
* Enforces that token is a part of the returned value.
|
||||
*/
|
||||
export function useSyncWidgetInputs({
|
||||
token,
|
||||
onTokenChange,
|
||||
defaultTokens,
|
||||
onDefaultTokenChange,
|
||||
}: {
|
||||
token?: Currency
|
||||
onTokenChange?: (token: Currency) => void
|
||||
defaultTokens: DefaultTokens
|
||||
onDefaultTokenChange?: (token: Currency) => void
|
||||
}) {
|
||||
const trace = useTrace({ section: InterfaceSectionName.WIDGET })
|
||||
|
||||
const { chainId } = useWeb3React()
|
||||
const previousChainId = usePrevious(chainId)
|
||||
|
||||
const [type, setType] = useState<SwapValue['type']>(TradeType.EXACT_INPUT)
|
||||
const [amount, setAmount] = useState<SwapValue['amount']>(EMPTY_AMOUNT)
|
||||
const [tokens, setTokens] = useState<SwapTokens>({ [Field.OUTPUT]: token, default: token })
|
||||
const [tokens, setTokens] = useState<SwapTokens>(defaultTokens)
|
||||
|
||||
useEffect(() => {
|
||||
setTokens((tokens) => {
|
||||
const update = { ...tokens, default: token }
|
||||
if (!includesDefaultToken(update)) {
|
||||
return { [Field.OUTPUT]: update.default, default: update.default }
|
||||
}
|
||||
return update
|
||||
})
|
||||
}, [token])
|
||||
if (!tokens[Field.INPUT] && !tokens[Field.OUTPUT]) {
|
||||
setTokens({
|
||||
...tokens,
|
||||
[Field.INPUT]: defaultTokens[Field.INPUT] ?? tokens[Field.INPUT],
|
||||
[Field.OUTPUT]: defaultTokens[Field.OUTPUT] ?? tokens[Field.OUTPUT] ?? defaultTokens.default,
|
||||
default: defaultTokens.default,
|
||||
})
|
||||
}
|
||||
}, [defaultTokens, tokens])
|
||||
|
||||
useEffect(() => {
|
||||
if (chainId !== previousChainId && !!previousChainId) {
|
||||
setTokens({
|
||||
...tokens,
|
||||
[Field.INPUT]: undefined,
|
||||
[Field.OUTPUT]: undefined,
|
||||
})
|
||||
setAmount(EMPTY_AMOUNT)
|
||||
}
|
||||
}, [chainId, previousChainId, tokens])
|
||||
|
||||
const onAmountChange = useCallback(
|
||||
(field: Field, amount: string, origin?: 'max') => {
|
||||
@@ -96,13 +114,14 @@ export function useSyncWidgetInputs({
|
||||
return type
|
||||
})
|
||||
|
||||
if (!includesDefaultToken(update)) {
|
||||
onTokenChange?.(update[Field.OUTPUT] || selectingToken)
|
||||
if (missingDefaultToken(update)) {
|
||||
onDefaultTokenChange?.(update[Field.OUTPUT] ?? selectingToken)
|
||||
}
|
||||
setTokens(update)
|
||||
},
|
||||
[onTokenChange, selectingField, tokens]
|
||||
[onDefaultTokenChange, selectingField, tokens]
|
||||
)
|
||||
|
||||
const tokenSelector = (
|
||||
<CurrencySearchModal
|
||||
isOpen={selectingField !== undefined}
|
||||
@@ -110,6 +129,7 @@ export function useSyncWidgetInputs({
|
||||
selectedCurrency={selectingField && tokens[selectingField]}
|
||||
otherSelectedCurrency={selectingField && tokens[invertField(selectingField)]}
|
||||
onCurrencySelect={onTokenSelect}
|
||||
showCommonBases
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -117,11 +137,11 @@ export function useSyncWidgetInputs({
|
||||
() => ({
|
||||
type,
|
||||
amount,
|
||||
// If the default has not yet been handled, preemptively disable the widget by passing no tokens. Effectively,
|
||||
// If the initial state has not yet been set, preemptively disable the widget by passing no tokens. Effectively,
|
||||
// this resets the widget - avoiding rendering stale state - because with no tokens the skeleton will be rendered.
|
||||
...(token && tokens.default?.equals(token) ? tokens : undefined),
|
||||
...(tokens[Field.INPUT] || tokens[Field.OUTPUT] ? tokens : undefined),
|
||||
}),
|
||||
[amount, token, tokens, type]
|
||||
[amount, tokens, type]
|
||||
)
|
||||
const valueHandlers: SwapEventHandlers = useMemo(
|
||||
() => ({ onAmountChange, onSwitchTokens, onTokenSelectorClick }),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { Slippage, SwapController, SwapEventHandlers } from '@uniswap/widgets'
|
||||
import { RouterPreference, Slippage, SwapController, SwapEventHandlers } from '@uniswap/widgets'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
|
||||
@@ -37,6 +37,8 @@ export function useSyncWidgetSettings() {
|
||||
[setAppSlippage]
|
||||
)
|
||||
|
||||
const [routerPreference, onRouterPreferenceChange] = useState(RouterPreference.API)
|
||||
|
||||
const onSettingsReset = useCallback(() => {
|
||||
setWidgetTtl(undefined)
|
||||
setAppTtl(DEFAULT_DEADLINE_FROM_NOW)
|
||||
@@ -46,11 +48,15 @@ export function useSyncWidgetSettings() {
|
||||
|
||||
const settings: SwapController['settings'] = useMemo(() => {
|
||||
const auto = appSlippage === 'auto'
|
||||
return { slippage: { auto, max: widgetSlippage }, transactionTtl: widgetTtl }
|
||||
}, [widgetSlippage, widgetTtl, appSlippage])
|
||||
return {
|
||||
slippage: { auto, max: widgetSlippage },
|
||||
transactionTtl: widgetTtl,
|
||||
routerPreference,
|
||||
}
|
||||
}, [appSlippage, widgetSlippage, widgetTtl, routerPreference])
|
||||
const settingsHandlers: SwapEventHandlers = useMemo(
|
||||
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange }),
|
||||
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange]
|
||||
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange, onRouterPreferenceChange }),
|
||||
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange, onRouterPreferenceChange]
|
||||
)
|
||||
|
||||
return { settings: { settings, ...settingsHandlers } }
|
||||
|
||||
@@ -1,45 +1,68 @@
|
||||
import { Theme } from '@uniswap/widgets'
|
||||
import { darkTheme, lightTheme } from 'theme/colors'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
const zIndex = {
|
||||
modal: Z_INDEX.modal,
|
||||
}
|
||||
|
||||
const fonts = {
|
||||
fontFamily: 'Inter custom',
|
||||
}
|
||||
|
||||
export const LIGHT_THEME = {
|
||||
export const LIGHT_THEME: Theme = {
|
||||
// surface
|
||||
container: lightTheme.backgroundSurface,
|
||||
interactive: lightTheme.backgroundInteractive,
|
||||
module: lightTheme.backgroundModule,
|
||||
accent: lightTheme.accentAction,
|
||||
dialog: lightTheme.backgroundBackdrop,
|
||||
accentSoft: lightTheme.accentActionSoft,
|
||||
container: lightTheme.backgroundSurface,
|
||||
module: lightTheme.backgroundModule,
|
||||
interactive: lightTheme.backgroundInteractive,
|
||||
outline: lightTheme.backgroundOutline,
|
||||
dialog: lightTheme.backgroundBackdrop,
|
||||
scrim: lightTheme.backgroundScrim,
|
||||
// text
|
||||
onAccent: lightTheme.white,
|
||||
primary: lightTheme.textPrimary,
|
||||
secondary: lightTheme.textSecondary,
|
||||
hint: lightTheme.textTertiary,
|
||||
onInteractive: lightTheme.accentTextDarkPrimary,
|
||||
// shadow
|
||||
deepShadow: lightTheme.deepShadow,
|
||||
networkDefaultShadow: lightTheme.networkDefaultShadow,
|
||||
|
||||
// state
|
||||
success: lightTheme.accentSuccess,
|
||||
warning: lightTheme.accentWarning,
|
||||
error: lightTheme.accentCritical,
|
||||
|
||||
...fonts,
|
||||
zIndex,
|
||||
}
|
||||
|
||||
export const DARK_THEME = {
|
||||
export const DARK_THEME: Theme = {
|
||||
// surface
|
||||
container: darkTheme.backgroundSurface,
|
||||
interactive: darkTheme.backgroundInteractive,
|
||||
module: darkTheme.backgroundModule,
|
||||
accent: darkTheme.accentAction,
|
||||
dialog: darkTheme.backgroundBackdrop,
|
||||
accentSoft: darkTheme.accentActionSoft,
|
||||
container: darkTheme.backgroundSurface,
|
||||
module: darkTheme.backgroundModule,
|
||||
interactive: darkTheme.backgroundInteractive,
|
||||
outline: darkTheme.backgroundOutline,
|
||||
dialog: darkTheme.backgroundBackdrop,
|
||||
scrim: darkTheme.backgroundScrim,
|
||||
// text
|
||||
onAccent: darkTheme.white,
|
||||
primary: darkTheme.textPrimary,
|
||||
secondary: darkTheme.textSecondary,
|
||||
hint: darkTheme.textTertiary,
|
||||
onInteractive: darkTheme.accentTextLightPrimary,
|
||||
// shadow
|
||||
deepShadow: darkTheme.deepShadow,
|
||||
networkDefaultShadow: darkTheme.networkDefaultShadow,
|
||||
// state
|
||||
success: darkTheme.accentSuccess,
|
||||
warning: darkTheme.accentWarning,
|
||||
error: darkTheme.accentCritical,
|
||||
|
||||
...fonts,
|
||||
zIndex,
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceEventName, InterfaceSectionName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent } from '@uniswap/sdk-core'
|
||||
import {
|
||||
OnTxSuccess,
|
||||
TradeType,
|
||||
Transaction,
|
||||
TransactionEventHandlers,
|
||||
TransactionInfo,
|
||||
TransactionType,
|
||||
TransactionType as WidgetTransactionType,
|
||||
} from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WrapType } from 'hooks/useWrapCallback'
|
||||
import { formatSwapSignedAnalyticsEventProperties, formatToDecimal, getTokenAddress } from 'lib/utils/analytics'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
formatSwapSignedAnalyticsEventProperties,
|
||||
formatToDecimal,
|
||||
getTokenAddress,
|
||||
} from 'lib/utils/analytics'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTransactionAdder } from 'state/transactions/hooks'
|
||||
import {
|
||||
@@ -19,6 +27,42 @@ import {
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
|
||||
interface AnalyticsEventProps {
|
||||
trade: Trade<Currency, Currency, TradeType>
|
||||
gasUsed: string | undefined
|
||||
blockNumber: number | undefined
|
||||
hash: string | undefined
|
||||
allowedSlippage: Percent
|
||||
succeeded: boolean
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = ({
|
||||
trade,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
succeeded,
|
||||
gasUsed,
|
||||
blockNumber,
|
||||
}: AnalyticsEventProps) => ({
|
||||
estimated_network_fee_usd: gasUsed,
|
||||
transaction_hash: hash,
|
||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
swap_quote_block_number: blockNumber,
|
||||
succeeded,
|
||||
})
|
||||
|
||||
/** Integrates the Widget's transactions, showing the widget's transactions in the app. */
|
||||
export function useSyncWidgetTransactions() {
|
||||
@@ -46,7 +90,7 @@ export function useSyncWidgetTransactions() {
|
||||
amount: transactionAmount
|
||||
? formatToDecimal(transactionAmount, transactionAmount?.currency.decimals)
|
||||
: undefined,
|
||||
type: type === WidgetTransactionType.WRAP ? WrapType.WRAP : WrapType.UNWRAP,
|
||||
type: type === WidgetTransactionType.WRAP ? TransactionType.WRAP : TransactionType.UNWRAP,
|
||||
...trace,
|
||||
}
|
||||
sendAnalyticsEvent(InterfaceEventName.WRAP_TOKEN_TXN_SUBMITTED, eventProperties)
|
||||
@@ -94,7 +138,24 @@ export function useSyncWidgetTransactions() {
|
||||
[addTransaction, chainId, trace]
|
||||
)
|
||||
|
||||
const txHandlers: TransactionEventHandlers = useMemo(() => ({ onTxSubmit }), [onTxSubmit])
|
||||
const onTxSuccess: OnTxSuccess = useCallback((hash: string, tx) => {
|
||||
if (tx.info.type === TransactionType.SWAP) {
|
||||
const { trade, slippageTolerance } = tx.info
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_TRANSACTION_COMPLETED,
|
||||
formatAnalyticsEventProperties({
|
||||
trade,
|
||||
hash,
|
||||
gasUsed: tx.receipt?.gasUsed?.toString(),
|
||||
blockNumber: tx.receipt?.blockNumber,
|
||||
allowedSlippage: slippageTolerance,
|
||||
succeeded: tx.receipt?.status === 1,
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const txHandlers: TransactionEventHandlers = useMemo(() => ({ onTxSubmit, onTxSuccess }), [onTxSubmit, onTxSuccess])
|
||||
|
||||
return { transactions: { ...txHandlers } }
|
||||
}
|
||||
|
||||
@@ -25,22 +25,10 @@ export interface Connection {
|
||||
type: ConnectionType
|
||||
}
|
||||
|
||||
type MetaMaskError = Error & { code: number }
|
||||
|
||||
let metaMaskErrorHandler: (error: MetaMaskError) => void | undefined
|
||||
export function setMetMaskErrorHandler(errorHandler: typeof metaMaskErrorHandler) {
|
||||
metaMaskErrorHandler = errorHandler
|
||||
}
|
||||
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
function onMetaMaskError(error: Error) {
|
||||
onError(error)
|
||||
metaMaskErrorHandler?.(error as MetaMaskError)
|
||||
}
|
||||
|
||||
const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: RPC_PROVIDERS, defaultChainId: 1 })
|
||||
)
|
||||
@@ -50,9 +38,7 @@ export const networkConnection: Connection = {
|
||||
type: ConnectionType.NETWORK,
|
||||
}
|
||||
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>(
|
||||
(actions) => new MetaMask({ actions, onError: onMetaMaskError })
|
||||
)
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
export const injectedConnection: Connection = {
|
||||
connector: web3Injected,
|
||||
hooks: web3InjectedHooks,
|
||||
|
||||
@@ -12,18 +12,21 @@ export function getIsInjected(): boolean {
|
||||
return Boolean(window.ethereum)
|
||||
}
|
||||
|
||||
export function getHasMetaMaskExtensionInstalled(): boolean {
|
||||
return window.ethereum?.isMetaMask ?? false
|
||||
export function getIsBraveWallet(): boolean {
|
||||
return window.ethereum?.isBraveWallet ?? false
|
||||
}
|
||||
|
||||
export function getHasCoinbaseExtensionInstalled(): boolean {
|
||||
export function getIsMetaMaskWallet(): boolean {
|
||||
// When using Brave browser, `isMetaMask` is set to true when using the built-in wallet
|
||||
// This function should return true only when using the MetaMask extension
|
||||
// https://wallet-docs.brave.com/ethereum/wallet-detection#compatability-with-metamask
|
||||
return (window.ethereum?.isMetaMask ?? false) && !getIsBraveWallet()
|
||||
}
|
||||
|
||||
export function getIsCoinbaseWallet(): boolean {
|
||||
return window.ethereum?.isCoinbaseWallet ?? false
|
||||
}
|
||||
|
||||
export function getIsMetaMask(connectionType: ConnectionType): boolean {
|
||||
return connectionType === ConnectionType.INJECTED && getHasMetaMaskExtensionInstalled()
|
||||
}
|
||||
|
||||
const CONNECTIONS = [
|
||||
gnosisSafeConnection,
|
||||
injectedConnection,
|
||||
@@ -56,7 +59,7 @@ export function getConnection(c: Connector | ConnectionType) {
|
||||
|
||||
export function getConnectionName(
|
||||
connectionType: ConnectionType,
|
||||
hasMetaMaskExtension: boolean = getHasMetaMaskExtensionInstalled()
|
||||
hasMetaMaskExtension: boolean = getIsMetaMaskWallet()
|
||||
) {
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
|
||||
@@ -9,8 +9,12 @@ class TokenLogoLookupTable {
|
||||
initialize() {
|
||||
const dict: { [key: string]: string[] | undefined } = {}
|
||||
|
||||
DEFAULT_LIST_OF_LISTS.forEach((list) =>
|
||||
store.getState().lists.byUrl[list].current?.tokens.forEach((token) => {
|
||||
DEFAULT_LIST_OF_LISTS.forEach((list) => {
|
||||
const listData = store.getState().lists.byUrl[list]
|
||||
if (!listData) {
|
||||
return
|
||||
}
|
||||
listData.current?.tokens.forEach((token) => {
|
||||
if (token.logoURI) {
|
||||
const lowercaseAddress = token.address.toLowerCase()
|
||||
const currentEntry = dict[lowercaseAddress + ':' + token.chainId]
|
||||
@@ -21,7 +25,7 @@ class TokenLogoLookupTable {
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
this.dict = dict
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
|
||||
},
|
||||
}
|
||||
|
||||
export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13
|
||||
// in PoS, ethereum block time is 12s, see https://ethereum.org/en/developers/docs/blocks/#block-time
|
||||
export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 12
|
||||
|
||||
// Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time
|
||||
export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const UNI_LIST = 'https://tokens.uniswap.org'
|
||||
export const UNI_EXTENDED_LIST = 'https://extendedtokens.uniswap.org/'
|
||||
const UNI_UNSUPPORTED_LIST = 'https://unsupportedtokens.uniswap.org/'
|
||||
export const UNI_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
|
||||
export const UNI_EXTENDED_LIST = 'https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org'
|
||||
const UNI_UNSUPPORTED_LIST = 'https://gateway.ipfs.io/ipns/unsupportedtokens.uniswap.org'
|
||||
const AAVE_LIST = 'tokenlist.aave.eth'
|
||||
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
|
||||
const CMC_ALL_LIST = 'https://api.coinmarketcap.com/data-api/v3/uniswap/all.json'
|
||||
@@ -8,7 +8,6 @@ const COINGECKO_LIST = 'https://tokens.coingecko.com/uniswap/all.json'
|
||||
const COMPOUND_LIST = 'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json'
|
||||
const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json'
|
||||
const KLEROS_LIST = 't2crtokens.eth'
|
||||
const ROLL_LIST = 'https://app.tryroll.com/tokens.json'
|
||||
const SET_LIST = 'https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json'
|
||||
const WRAPPED_LIST = 'wrapped.tokensoft.eth'
|
||||
|
||||
@@ -30,7 +29,6 @@ export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
||||
GEMINI_LIST,
|
||||
WRAPPED_LIST,
|
||||
SET_LIST,
|
||||
ROLL_LIST,
|
||||
ARBITRUM_LIST,
|
||||
OPTIMISM_LIST,
|
||||
CELO_LIST,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
|
||||
import { ZERO_ADDRESS } from './misc'
|
||||
import { NATIVE_CHAIN_ID } from './tokens'
|
||||
@@ -94,3 +96,11 @@ export function checkWarning(tokenAddress: string) {
|
||||
return BlockedWarning
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(cartcrom): Replace all usage of WARNING_LEVEL with SafetyLevel
|
||||
export function checkSearchTokenWarning(token: SearchToken) {
|
||||
if (!token.address) {
|
||||
return token.standard === TokenStandard.Native ? null : StrongWarning
|
||||
}
|
||||
return checkWarning(token.address)
|
||||
}
|
||||
|
||||
9
src/featureFlags/flags/dummyFeatureGate.ts
Normal file
9
src/featureFlags/flags/dummyFeatureGate.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
function useDummyGateFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.statsigDummy)
|
||||
}
|
||||
|
||||
export function useDummyGateEnabled(): boolean {
|
||||
return useDummyGateFlag() === BaseVariant.Enabled
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* The value here must match the value in the statsig dashboard, if you plan to use statsig.
|
||||
*/
|
||||
export enum FeatureFlag {
|
||||
fiatOnramp = 'fiatOnramp',
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
permit2 = 'permit2',
|
||||
nftListV2 = 'nftListV2',
|
||||
payWithAnyToken = 'payWithAnyToken',
|
||||
swapWidget = 'swap_widget_replacement_enabled',
|
||||
gqlRouting = 'gqlRouting',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useFiatOnrampFlag(): BaseVariant {
|
||||
return BaseVariant.Enabled
|
||||
// return useBaseFlag(FeatureFlag.fiatOnramp)
|
||||
}
|
||||
7
src/featureFlags/flags/gqlRouting.ts
Normal file
7
src/featureFlags/flags/gqlRouting.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useGqlRoutingFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.gqlRouting, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export { BaseVariant as GqlRoutingVariant }
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useNftListV2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.nftListV2)
|
||||
}
|
||||
|
||||
export { BaseVariant as NftListV2Variant }
|
||||
11
src/featureFlags/flags/nftlGraphql.ts
Normal file
11
src/featureFlags/flags/nftlGraphql.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useNftGraphqlFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.nftGraphql)
|
||||
}
|
||||
|
||||
export function useNftGraphqlEnabled(): boolean {
|
||||
return useNftGraphqlFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as NftGraphqlVariant }
|
||||
@@ -1,7 +1,24 @@
|
||||
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function usePayWithAnyTokenFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.payWithAnyToken)
|
||||
return useBaseFlag(FeatureFlag.payWithAnyToken, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export function usePayWithAnyTokenEnabled(): boolean {
|
||||
const flagEnabled = usePayWithAnyTokenFlag() === BaseVariant.Enabled
|
||||
const { chainId } = useWeb3React()
|
||||
try {
|
||||
// Detect if the Universal Router is not yet deployed to chainId.
|
||||
// This is necessary so that we can fallback correctly on chains without a Universal Router deployment.
|
||||
// It will be removed once Universal Router is deployed on all supported chains.
|
||||
chainId && UNIVERSAL_ROUTER_ADDRESS(chainId)
|
||||
return flagEnabled
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseVariant as PayWithAnyTokenVariant }
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function usePermit2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.permit2)
|
||||
return useBaseFlag(FeatureFlag.permit2, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export function usePermit2Enabled(): boolean {
|
||||
|
||||
11
src/featureFlags/flags/swapWidget.ts
Normal file
11
src/featureFlags/flags/swapWidget.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useSwapWidgetFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.swapWidget, BaseVariant.Control)
|
||||
}
|
||||
|
||||
export function useSwapWidgetEnabled(): boolean {
|
||||
return useSwapWidgetFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as SwapWidgetVariant }
|
||||
@@ -1,5 +1,6 @@
|
||||
import { atomWithStorage, useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { createContext, ReactNode, useCallback, useContext } from 'react'
|
||||
import { useGate } from 'statsig-react'
|
||||
export { FeatureFlag } from './flags/featureFlags'
|
||||
|
||||
interface FeatureFlagsContextType {
|
||||
@@ -56,7 +57,12 @@ export enum BaseVariant {
|
||||
}
|
||||
|
||||
export function useBaseFlag(flag: string, defaultValue = BaseVariant.Control): BaseVariant {
|
||||
switch (useFeatureFlagsContext().flags[flag]) {
|
||||
const { value: statsigValue } = useGate(flag) // non-existent gates return false
|
||||
const featureFlagsContext = useFeatureFlagsContext()
|
||||
if (statsigValue) {
|
||||
return BaseVariant.Enabled
|
||||
}
|
||||
switch (featureFlagsContext.flags[flag]) {
|
||||
case 'enabled':
|
||||
return BaseVariant.Enabled
|
||||
case 'control':
|
||||
|
||||
59
src/graphql/data/RecentlySearched.ts
Normal file
59
src/graphql/data/RecentlySearched.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
gql`
|
||||
query RecentlySearchedAssets($collectionAddresses: [String!]!, $contracts: [ContractInput!]!) {
|
||||
nftCollections(filter: { addresses: $collectionAddresses }) {
|
||||
edges {
|
||||
node {
|
||||
collectionId
|
||||
image {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
name
|
||||
numAssets
|
||||
nftContracts {
|
||||
address
|
||||
}
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens(contracts: $contracts) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
102
src/graphql/data/SearchTokens.ts
Normal file
102
src/graphql/data/SearchTokens.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import gql from 'graphql-tag'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { Chain, SearchTokensQuery, useSearchTokensQuery } from './__generated__/types-and-hooks'
|
||||
import { chainIdToBackendName } from './util'
|
||||
|
||||
gql`
|
||||
query SearchTokens($searchQuery: String!) {
|
||||
searchTokens(searchQuery: $searchQuery) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export type SearchToken = NonNullable<NonNullable<SearchTokensQuery['searchTokens']>[number]>
|
||||
|
||||
function isMoreRevelantToken(current: SearchToken, existing: SearchToken | undefined, searchChain: Chain) {
|
||||
if (!existing) return true
|
||||
|
||||
// Always priotize natives, and if both tokens are native, prefer native on current chain (i.e. Matic on Polygon over Matic on Mainnet )
|
||||
if (current.standard === 'NATIVE' && (existing.standard !== 'NATIVE' || current.chain === searchChain)) return true
|
||||
|
||||
// Prefer tokens on the searched chain, otherwise prefer mainnet tokens
|
||||
return current.chain === searchChain || (existing.chain !== searchChain && current.chain === Chain.Ethereum)
|
||||
}
|
||||
|
||||
// Places natives first, wrapped native on current chain next, then sorts by volume
|
||||
function searchTokenSortFunction(
|
||||
searchChain: Chain,
|
||||
wrappedNativeAddress: string | undefined,
|
||||
a: SearchToken,
|
||||
b: SearchToken
|
||||
) {
|
||||
if (a.standard === 'NATIVE') {
|
||||
if (b.standard === 'NATIVE') {
|
||||
if (a.chain === searchChain) return -1
|
||||
else if (b.chain === searchChain) return 1
|
||||
else return 0
|
||||
} else return -1
|
||||
} else if (b.standard === 'NATIVE') return 1
|
||||
else if (wrappedNativeAddress && a.address === wrappedNativeAddress) return -1
|
||||
else if (wrappedNativeAddress && b.address === wrappedNativeAddress) return 1
|
||||
else return (b.market?.volume24H?.value ?? 0) - (a.market?.volume24H?.value ?? 0)
|
||||
}
|
||||
|
||||
export function useSearchTokens(searchQuery: string, chainId: number) {
|
||||
const { data, loading, error } = useSearchTokensQuery({
|
||||
variables: {
|
||||
searchQuery,
|
||||
},
|
||||
})
|
||||
|
||||
const sortedTokens = useMemo(() => {
|
||||
const searchChain = chainIdToBackendName(chainId)
|
||||
// Stores results, allowing overwriting cross-chain tokens w/ more 'relevant token'
|
||||
const selectionMap: { [projectId: string]: SearchToken } = {}
|
||||
data?.searchTokens?.forEach((token) => {
|
||||
if (token.project?.id) {
|
||||
const existing = selectionMap[token.project.id]
|
||||
if (isMoreRevelantToken(token, existing, searchChain)) selectionMap[token.project.id] = token
|
||||
}
|
||||
})
|
||||
return Object.values(selectionMap).sort(
|
||||
searchTokenSortFunction.bind(null, searchChain, WRAPPED_NATIVE_CURRENCY[chainId]?.address)
|
||||
)
|
||||
}, [data, chainId])
|
||||
|
||||
return {
|
||||
data: sortedTokens,
|
||||
loading,
|
||||
error,
|
||||
}
|
||||
}
|
||||
@@ -14,40 +14,49 @@ The difference between Token and TokenProject:
|
||||
TokenProjectMarket is aggregated market data (aggregated over multiple dexes and centralized exchanges) that we get from coingecko.
|
||||
*/
|
||||
gql`
|
||||
query Token($contract: ContractInput!) {
|
||||
tokens(contracts: [$contract]) {
|
||||
query Token($chain: Chain!, $address: String = null) {
|
||||
token(chain: $chain, address: $address) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
address
|
||||
symbol
|
||||
standard
|
||||
market(currency: USD) {
|
||||
id
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
description
|
||||
homepageUrl
|
||||
twitterName
|
||||
logoUrl
|
||||
tokens {
|
||||
id
|
||||
chain
|
||||
address
|
||||
}
|
||||
@@ -58,7 +67,7 @@ gql`
|
||||
|
||||
export type { Chain, TokenQuery } from './__generated__/types-and-hooks'
|
||||
|
||||
export type TokenQueryData = NonNullable<TokenQuery['tokens']>[number]
|
||||
export type TokenQueryData = TokenQuery['token']
|
||||
|
||||
// TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces.
|
||||
export class QueryToken extends WrappedTokenInfo {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
// TODO: Implemnt this as a refetchable fragment on tokenQuery when backend adds support
|
||||
gql`
|
||||
query TokenPrice($contract: ContractInput!, $duration: HistoryDuration!) {
|
||||
tokens(contracts: [$contract]) {
|
||||
query TokenPrice($chain: Chain!, $address: String = null, $duration: HistoryDuration!) {
|
||||
token(chain: $chain, address: $address) {
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
|
||||
@@ -15,7 +15,15 @@ import {
|
||||
useTopTokens100Query,
|
||||
useTopTokensSparklineQuery,
|
||||
} from './__generated__/types-and-hooks'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, isPricePoint, PricePoint, toHistoryDuration, unwrapToken } from './util'
|
||||
import {
|
||||
CHAIN_NAME_TO_CHAIN_ID,
|
||||
isPricePoint,
|
||||
PollingInterval,
|
||||
PricePoint,
|
||||
toHistoryDuration,
|
||||
unwrapToken,
|
||||
usePollQueryWhileMounted,
|
||||
} from './util'
|
||||
|
||||
gql`
|
||||
query TopTokens100($duration: HistoryDuration!, $chain: Chain!) {
|
||||
@@ -25,25 +33,32 @@ gql`
|
||||
chain
|
||||
address
|
||||
symbol
|
||||
standard
|
||||
market(currency: USD) {
|
||||
id
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: $duration) {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
volume(duration: $duration) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
@@ -53,9 +68,13 @@ gql`
|
||||
gql`
|
||||
query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) {
|
||||
topTokens(pageSize: 100, page: 1, chain: $chain) {
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
id
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
@@ -64,11 +83,12 @@ gql`
|
||||
}
|
||||
`
|
||||
|
||||
function useSortedTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
|
||||
function useSortedTokens(tokens: TopTokens100Query['topTokens']) {
|
||||
const sortMethod = useAtomValue(sortMethodAtom)
|
||||
const sortAscending = useAtomValue(sortAscendingAtom)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!tokens) return undefined
|
||||
let tokenArray = Array.from(tokens)
|
||||
switch (sortMethod) {
|
||||
case TokenSortMethod.PRICE:
|
||||
@@ -93,12 +113,13 @@ function useSortedTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
|
||||
}, [tokens, sortMethod, sortAscending])
|
||||
}
|
||||
|
||||
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['topTokens']>) {
|
||||
function useFilteredTokens(tokens: TopTokens100Query['topTokens']) {
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
|
||||
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
|
||||
|
||||
return useMemo(() => {
|
||||
if (!tokens) return undefined
|
||||
let returnTokens = tokens
|
||||
if (lowercaseFilterString) {
|
||||
returnTokens = returnTokens?.filter((token) => {
|
||||
@@ -119,7 +140,7 @@ export type TopToken = NonNullable<NonNullable<TopTokens100Query>['topTokens']>[
|
||||
|
||||
interface UseTopTokensReturnValue {
|
||||
tokens: TopToken[] | undefined
|
||||
tokenVolumeRank: Record<string, number>
|
||||
tokenSortRank: Record<string, number>
|
||||
loadingTokens: boolean
|
||||
sparklines: SparklineMap
|
||||
}
|
||||
@@ -128,9 +149,12 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
|
||||
|
||||
const { data: sparklineQuery } = useTopTokensSparklineQuery({
|
||||
variables: { duration, chain },
|
||||
})
|
||||
const { data: sparklineQuery } = usePollQueryWhileMounted(
|
||||
useTopTokensSparklineQuery({
|
||||
variables: { duration, chain },
|
||||
}),
|
||||
PollingInterval.Slow
|
||||
)
|
||||
|
||||
const sparklines = useMemo(() => {
|
||||
const unwrappedTokens = sparklineQuery?.topTokens?.map((topToken) => unwrapToken(chainId, topToken))
|
||||
@@ -141,33 +165,29 @@ export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
|
||||
return map
|
||||
}, [chainId, sparklineQuery?.topTokens])
|
||||
|
||||
const { data, loading: loadingTokens } = useTopTokens100Query({
|
||||
variables: { duration, chain },
|
||||
})
|
||||
const unwrappedTokens = useMemo(
|
||||
() => data?.topTokens?.map((token) => unwrapToken(chainId, token)) ?? [],
|
||||
[chainId, data]
|
||||
const { data, loading: loadingTokens } = usePollQueryWhileMounted(
|
||||
useTopTokens100Query({
|
||||
variables: { duration, chain },
|
||||
}),
|
||||
PollingInterval.Fast
|
||||
)
|
||||
const tokenVolumeRank = useMemo(
|
||||
|
||||
const unwrappedTokens = useMemo(() => data?.topTokens?.map((token) => unwrapToken(chainId, token)), [chainId, data])
|
||||
const sortedTokens = useSortedTokens(unwrappedTokens)
|
||||
const tokenSortRank = useMemo(
|
||||
() =>
|
||||
unwrappedTokens
|
||||
.sort((a, b) => {
|
||||
if (!a.market?.volume || !b.market?.volume) return 0
|
||||
return a.market.volume.value > b.market.volume.value ? -1 : 1
|
||||
})
|
||||
.reduce((acc, cur, i) => {
|
||||
if (!cur.address) return acc
|
||||
return {
|
||||
...acc,
|
||||
[cur.address]: i + 1,
|
||||
}
|
||||
}, {}),
|
||||
[unwrappedTokens]
|
||||
sortedTokens?.reduce((acc, cur, i) => {
|
||||
if (!cur.address) return acc
|
||||
return {
|
||||
...acc,
|
||||
[cur.address]: i + 1,
|
||||
}
|
||||
}, {}) ?? {},
|
||||
[sortedTokens]
|
||||
)
|
||||
const filteredTokens = useFilteredTokens(unwrappedTokens)
|
||||
const sortedTokens = useSortedTokens(filteredTokens)
|
||||
const filteredTokens = useFilteredTokens(sortedTokens)
|
||||
return useMemo(
|
||||
() => ({ tokens: sortedTokens, tokenVolumeRank, loadingTokens, sparklines }),
|
||||
[loadingTokens, tokenVolumeRank, sortedTokens, sparklines]
|
||||
() => ({ tokens: filteredTokens, tokenSortRank, loadingTokens, sparklines }),
|
||||
[filteredTokens, tokenSortRank, loadingTokens, sparklines]
|
||||
)
|
||||
}
|
||||
|
||||
51
src/graphql/data/TrendingTokens.ts
Normal file
51
src/graphql/data/TrendingTokens.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useTrendingTokensQuery } from './__generated__/types-and-hooks'
|
||||
import { chainIdToBackendName, unwrapToken } from './util'
|
||||
|
||||
gql`
|
||||
query TrendingTokens($chain: Chain!) {
|
||||
topTokens(pageSize: 4, page: 1, chain: $chain, orderBy: VOLUME) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function useTrendingTokens(chainId?: number) {
|
||||
const chain = chainIdToBackendName(chainId)
|
||||
const { data, loading } = useTrendingTokensQuery({ variables: { chain } })
|
||||
|
||||
return useMemo(
|
||||
() => ({ data: data?.topTokens?.map((token) => unwrapToken(chainId ?? 1, token)), loading }),
|
||||
[chainId, data?.topTokens, loading]
|
||||
)
|
||||
}
|
||||
671
src/graphql/data/__generated__/types-and-hooks.ts
generated
671
src/graphql/data/__generated__/types-and-hooks.ts
generated
@@ -82,7 +82,12 @@ export enum Chain {
|
||||
Ethereum = 'ETHEREUM',
|
||||
EthereumGoerli = 'ETHEREUM_GOERLI',
|
||||
Optimism = 'OPTIMISM',
|
||||
Polygon = 'POLYGON'
|
||||
Polygon = 'POLYGON',
|
||||
UnknownChain = 'UNKNOWN_CHAIN'
|
||||
}
|
||||
|
||||
export enum CollectionSortableField {
|
||||
Volume = 'VOLUME'
|
||||
}
|
||||
|
||||
export type ContractInput = {
|
||||
@@ -139,6 +144,49 @@ export enum MarketSortableField {
|
||||
Volume = 'VOLUME'
|
||||
}
|
||||
|
||||
export type NftActivity = {
|
||||
__typename?: 'NftActivity';
|
||||
address: Scalars['String'];
|
||||
asset?: Maybe<NftAsset>;
|
||||
fromAddress: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
marketplace?: Maybe<NftMarketplace>;
|
||||
orderStatus?: Maybe<OrderStatus>;
|
||||
price?: Maybe<Amount>;
|
||||
quantity?: Maybe<Scalars['Int']>;
|
||||
timestamp: Scalars['Int'];
|
||||
toAddress?: Maybe<Scalars['String']>;
|
||||
tokenId?: Maybe<Scalars['String']>;
|
||||
transactionHash?: Maybe<Scalars['String']>;
|
||||
type: NftActivityType;
|
||||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type NftActivityConnection = {
|
||||
__typename?: 'NftActivityConnection';
|
||||
edges: Array<NftActivityEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type NftActivityEdge = {
|
||||
__typename?: 'NftActivityEdge';
|
||||
cursor: Scalars['String'];
|
||||
node: NftActivity;
|
||||
};
|
||||
|
||||
export type NftActivityFilterInput = {
|
||||
activityTypes?: InputMaybe<Array<NftActivityType>>;
|
||||
address?: InputMaybe<Scalars['String']>;
|
||||
tokenId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum NftActivityType {
|
||||
CancelListing = 'CANCEL_LISTING',
|
||||
Listing = 'LISTING',
|
||||
Sale = 'SALE',
|
||||
Transfer = 'TRANSFER'
|
||||
}
|
||||
|
||||
export type NftApproval = {
|
||||
__typename?: 'NftApproval';
|
||||
approvedAddress: Scalars['String'];
|
||||
@@ -327,6 +375,8 @@ export type NftCollectionMarket = {
|
||||
marketplaces?: Maybe<Array<NftCollectionMarketplace>>;
|
||||
nftContracts?: Maybe<Array<NftContract>>;
|
||||
owners?: Maybe<Scalars['Int']>;
|
||||
percentListed?: Maybe<TimestampedAmount>;
|
||||
percentUniqueOwners?: Maybe<TimestampedAmount>;
|
||||
sales?: Maybe<TimestampedAmount>;
|
||||
totalVolume?: Maybe<TimestampedAmount>;
|
||||
volume?: Maybe<TimestampedAmount>;
|
||||
@@ -386,6 +436,7 @@ export type NftCollectionTraitStats = {
|
||||
|
||||
export type NftCollectionsFilterInput = {
|
||||
addresses?: InputMaybe<Array<Scalars['String']>>;
|
||||
nameQuery?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type NftContract = IContract & {
|
||||
@@ -468,12 +519,45 @@ export enum NftRarityProvider {
|
||||
RaritySniper = 'RARITY_SNIPER'
|
||||
}
|
||||
|
||||
export type NftRouteResponse = {
|
||||
__typename?: 'NftRouteResponse';
|
||||
calldata: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
route?: Maybe<Array<NftTrade>>;
|
||||
sendAmount: TokenAmount;
|
||||
toAddress: Scalars['String'];
|
||||
};
|
||||
|
||||
export enum NftStandard {
|
||||
Erc721 = 'ERC721',
|
||||
Erc1155 = 'ERC1155',
|
||||
Noncompliant = 'NONCOMPLIANT'
|
||||
}
|
||||
|
||||
export type NftTrade = {
|
||||
__typename?: 'NftTrade';
|
||||
amount: Scalars['Int'];
|
||||
contractAddress: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
marketplace: NftMarketplace;
|
||||
/** price represents the current price of the NFT, which can be different from quotePrice */
|
||||
price: TokenAmount;
|
||||
/** quotePrice represents the last quoted price of the NFT */
|
||||
quotePrice?: Maybe<TokenAmount>;
|
||||
tokenId: Scalars['String'];
|
||||
tokenType: NftStandard;
|
||||
};
|
||||
|
||||
export type NftTradeInput = {
|
||||
amount: Scalars['Int'];
|
||||
contractAddress: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
marketplace: NftMarketplace;
|
||||
quotePrice?: InputMaybe<TokenAmountInput>;
|
||||
tokenId: Scalars['String'];
|
||||
tokenType: NftStandard;
|
||||
};
|
||||
|
||||
export type NftTransfer = {
|
||||
__typename?: 'NftTransfer';
|
||||
asset: NftAsset;
|
||||
@@ -504,6 +588,36 @@ export type PageInfo = {
|
||||
startCursor?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** v2 pool parameters as defined by https://github.com/Uniswap/v2-sdk/blob/main/src/entities/pair.ts */
|
||||
export type PairInput = {
|
||||
tokenAmountA: TokenAmountInput;
|
||||
tokenAmountB: TokenAmountInput;
|
||||
};
|
||||
|
||||
export type PermitDetailsInput = {
|
||||
amount: Scalars['String'];
|
||||
expiration: Scalars['String'];
|
||||
nonce: Scalars['String'];
|
||||
token: Scalars['String'];
|
||||
};
|
||||
|
||||
export type PermitInput = {
|
||||
details: PermitDetailsInput;
|
||||
sigDeadline: Scalars['String'];
|
||||
signature: Scalars['String'];
|
||||
spender: Scalars['String'];
|
||||
};
|
||||
|
||||
/** v3 pool parameters as defined by https://github.com/Uniswap/v3-sdk/blob/main/src/entities/pool.ts */
|
||||
export type PoolInput = {
|
||||
fee: Scalars['Int'];
|
||||
liquidity: Scalars['String'];
|
||||
sqrtRatioX96: Scalars['String'];
|
||||
tickCurrent: Scalars['String'];
|
||||
tokenA: TokenInput;
|
||||
tokenB: TokenInput;
|
||||
};
|
||||
|
||||
export type Portfolio = {
|
||||
__typename?: 'Portfolio';
|
||||
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
|
||||
@@ -518,6 +632,7 @@ export type Portfolio = {
|
||||
|
||||
|
||||
export type PortfolioAssetActivitiesArgs = {
|
||||
includeOffChain?: InputMaybe<Scalars['Boolean']>;
|
||||
page?: InputMaybe<Scalars['Int']>;
|
||||
pageSize?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
@@ -529,25 +644,28 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = {
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
|
||||
nftActivity?: Maybe<NftActivityConnection>;
|
||||
nftAssets?: Maybe<NftAssetConnection>;
|
||||
nftBalances?: Maybe<NftBalanceConnection>;
|
||||
nftCollections?: Maybe<NftCollectionConnection>;
|
||||
nftCollectionsById?: Maybe<Array<Maybe<NftCollection>>>;
|
||||
nftRoute?: Maybe<NftRouteResponse>;
|
||||
portfolios?: Maybe<Array<Maybe<Portfolio>>>;
|
||||
searchTokenProjects?: Maybe<Array<Maybe<TokenProject>>>;
|
||||
searchTokens?: Maybe<Array<Maybe<Token>>>;
|
||||
token?: Maybe<Token>;
|
||||
tokenProjects?: Maybe<Array<Maybe<TokenProject>>>;
|
||||
tokens?: Maybe<Array<Maybe<Token>>>;
|
||||
topCollections?: Maybe<NftCollectionConnection>;
|
||||
topTokens?: Maybe<Array<Maybe<Token>>>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryAssetActivitiesArgs = {
|
||||
address: Scalars['String'];
|
||||
page?: InputMaybe<Scalars['Int']>;
|
||||
pageSize?: InputMaybe<Scalars['Int']>;
|
||||
export type QueryNftActivityArgs = {
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftActivityFilterInput>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -557,9 +675,11 @@ export type QueryNftAssetsArgs = {
|
||||
asc?: InputMaybe<Scalars['Boolean']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftAssetsFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
orderBy?: InputMaybe<NftAssetSortableField>;
|
||||
};
|
||||
|
||||
@@ -568,19 +688,20 @@ export type QueryNftBalancesArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftBalancesFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
ownerAddress: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryNftCollectionsArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftCollectionsFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -589,9 +710,16 @@ export type QueryNftCollectionsByIdArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryNftRouteArgs = {
|
||||
chain?: InputMaybe<Chain>;
|
||||
nftTrades: Array<NftTradeInput>;
|
||||
senderAddress: Scalars['String'];
|
||||
tokenTrades?: InputMaybe<Array<TokenTradeInput>>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryPortfoliosArgs = {
|
||||
ownerAddresses: Array<Scalars['String']>;
|
||||
useAltDataSource?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -621,6 +749,15 @@ export type QueryTokensArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryTopCollectionsArgs = {
|
||||
chains?: InputMaybe<Array<Chain>>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
duration?: InputMaybe<HistoryDuration>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
orderBy?: InputMaybe<CollectionSortableField>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryTopTokensArgs = {
|
||||
chain?: InputMaybe<Chain>;
|
||||
orderBy?: InputMaybe<TokenSortableField>;
|
||||
@@ -661,6 +798,18 @@ export type TokenMarketArgs = {
|
||||
currency?: InputMaybe<Currency>;
|
||||
};
|
||||
|
||||
export type TokenAmount = {
|
||||
__typename?: 'TokenAmount';
|
||||
currency: Currency;
|
||||
id: Scalars['ID'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type TokenAmountInput = {
|
||||
amount: Scalars['String'];
|
||||
token: TokenInput;
|
||||
};
|
||||
|
||||
export type TokenApproval = {
|
||||
__typename?: 'TokenApproval';
|
||||
approvedAddress: Scalars['String'];
|
||||
@@ -683,6 +832,13 @@ export type TokenBalance = {
|
||||
tokenProjectMarket?: Maybe<TokenProjectMarket>;
|
||||
};
|
||||
|
||||
export type TokenInput = {
|
||||
address: Scalars['String'];
|
||||
chainId: Scalars['Int'];
|
||||
decimals: Scalars['Int'];
|
||||
isNative: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type TokenMarket = {
|
||||
__typename?: 'TokenMarket';
|
||||
id: Scalars['ID'];
|
||||
@@ -741,6 +897,7 @@ export type TokenProjectMarketsArgs = {
|
||||
export type TokenProjectMarket = {
|
||||
__typename?: 'TokenProjectMarket';
|
||||
currency: Currency;
|
||||
/** @deprecated Use marketCap */
|
||||
fullyDilutedMarketCap?: Maybe<Amount>;
|
||||
id: Scalars['ID'];
|
||||
marketCap?: Maybe<Amount>;
|
||||
@@ -748,9 +905,12 @@ export type TokenProjectMarket = {
|
||||
priceHighLow?: Maybe<Amount>;
|
||||
priceHistory?: Maybe<Array<Maybe<TimestampedAmount>>>;
|
||||
pricePercentChange?: Maybe<Amount>;
|
||||
/** @deprecated Use pricePercentChange */
|
||||
pricePercentChange24h?: Maybe<Amount>;
|
||||
tokenProject: TokenProject;
|
||||
/** @deprecated Use TokenMarket.volume for Uniswap volume */
|
||||
volume?: Maybe<Amount>;
|
||||
/** @deprecated Use TokenMarket.volume with duration DAY for Uniswap volume */
|
||||
volume24h?: Maybe<Amount>;
|
||||
};
|
||||
|
||||
@@ -788,6 +948,31 @@ export enum TokenStandard {
|
||||
Native = 'NATIVE'
|
||||
}
|
||||
|
||||
export type TokenTradeInput = {
|
||||
permit?: InputMaybe<PermitInput>;
|
||||
routes?: InputMaybe<TokenTradeRoutesInput>;
|
||||
slippageToleranceBasisPoints?: InputMaybe<Scalars['Int']>;
|
||||
tokenAmount: TokenAmountInput;
|
||||
};
|
||||
|
||||
export type TokenTradeRouteInput = {
|
||||
inputAmount: TokenAmountInput;
|
||||
outputAmount: TokenAmountInput;
|
||||
pools: Array<TradePoolInput>;
|
||||
};
|
||||
|
||||
export type TokenTradeRoutesInput = {
|
||||
mixedRoutes?: InputMaybe<Array<TokenTradeRouteInput>>;
|
||||
tradeType: TokenTradeType;
|
||||
v2Routes?: InputMaybe<Array<TokenTradeRouteInput>>;
|
||||
v3Routes?: InputMaybe<Array<TokenTradeRouteInput>>;
|
||||
};
|
||||
|
||||
export enum TokenTradeType {
|
||||
ExactInput = 'EXACT_INPUT',
|
||||
ExactOutput = 'EXACT_OUTPUT'
|
||||
}
|
||||
|
||||
export type TokenTransfer = {
|
||||
__typename?: 'TokenTransfer';
|
||||
asset: Token;
|
||||
@@ -800,6 +985,11 @@ export type TokenTransfer = {
|
||||
transactedValue?: Maybe<Amount>;
|
||||
};
|
||||
|
||||
export type TradePoolInput = {
|
||||
pair?: InputMaybe<PairInput>;
|
||||
pool?: InputMaybe<PoolInput>;
|
||||
};
|
||||
|
||||
export type Transaction = {
|
||||
__typename?: 'Transaction';
|
||||
blockNumber: Scalars['Int'];
|
||||
@@ -825,20 +1015,37 @@ export enum TransactionStatus {
|
||||
Pending = 'PENDING'
|
||||
}
|
||||
|
||||
export type TokenQueryVariables = Exact<{
|
||||
contract: ContractInput;
|
||||
export type RecentlySearchedAssetsQueryVariables = Exact<{
|
||||
collectionAddresses: Array<Scalars['String']> | Scalars['String'];
|
||||
contracts: Array<ContractInput> | ContractInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type TokenQuery = { __typename?: 'Query', tokens?: Array<{ __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', totalValueLocked?: { __typename?: 'Amount', value: number, currency?: Currency }, price?: { __typename?: 'Amount', value: number, currency?: Currency }, volume24H?: { __typename?: 'Amount', value: number, currency?: Currency }, priceHigh52W?: { __typename?: 'Amount', value: number }, priceLow52W?: { __typename?: 'Amount', value: number } }, project?: { __typename?: 'TokenProject', description?: string, homepageUrl?: string, twitterName?: string, logoUrl?: string, tokens: Array<{ __typename?: 'Token', chain: Chain, address?: string }> } }> };
|
||||
export type RecentlySearchedAssetsQuery = { __typename?: 'Query', nftCollections?: { __typename?: 'NftCollectionConnection', edges: Array<{ __typename?: 'NftCollectionEdge', node: { __typename?: 'NftCollection', collectionId: string, isVerified?: boolean, name?: string, numAssets?: number, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, value: number } }> } }> }, tokens?: Array<{ __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, standard?: TokenStandard, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', id: string, price?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', id: string, value: number }, volume24H?: { __typename?: 'Amount', id: string, value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', id: string, logoUrl?: string, safetyLevel?: SafetyLevel } }> };
|
||||
|
||||
export type SearchTokensQueryVariables = Exact<{
|
||||
searchQuery: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type SearchTokensQuery = { __typename?: 'Query', searchTokens?: Array<{ __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, standard?: TokenStandard, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', id: string, price?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', id: string, value: number }, volume24H?: { __typename?: 'Amount', id: string, value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', id: string, logoUrl?: string, safetyLevel?: SafetyLevel } }> };
|
||||
|
||||
export type TokenQueryVariables = Exact<{
|
||||
chain: Chain;
|
||||
address?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type TokenQuery = { __typename?: 'Query', token?: { __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, address?: string, symbol?: string, standard?: TokenStandard, market?: { __typename?: 'TokenMarket', id: string, totalValueLocked?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, price?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, volume24H?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, priceHigh52W?: { __typename?: 'Amount', id: string, value: number }, priceLow52W?: { __typename?: 'Amount', id: string, value: number } }, project?: { __typename?: 'TokenProject', id: string, description?: string, homepageUrl?: string, twitterName?: string, logoUrl?: string, tokens: Array<{ __typename?: 'Token', id: string, chain: Chain, address?: string }> } } };
|
||||
|
||||
export type TokenPriceQueryVariables = Exact<{
|
||||
contract: ContractInput;
|
||||
chain: Chain;
|
||||
address?: InputMaybe<Scalars['String']>;
|
||||
duration: HistoryDuration;
|
||||
}>;
|
||||
|
||||
|
||||
export type TokenPriceQuery = { __typename?: 'Query', tokens?: Array<{ __typename?: 'Token', market?: { __typename?: 'TokenMarket', price?: { __typename?: 'Amount', value: number }, priceHistory?: Array<{ __typename?: 'TimestampedAmount', timestamp: number, value: number }> } }> };
|
||||
export type TokenPriceQuery = { __typename?: 'Query', token?: { __typename?: 'Token', id: string, address?: string, chain: Chain, market?: { __typename?: 'TokenMarket', id: string, price?: { __typename?: 'Amount', id: string, value: number }, priceHistory?: Array<{ __typename?: 'TimestampedAmount', id: string, timestamp: number, value: number }> } } };
|
||||
|
||||
export type TopTokens100QueryVariables = Exact<{
|
||||
duration: HistoryDuration;
|
||||
@@ -846,7 +1053,7 @@ export type TopTokens100QueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type TopTokens100Query = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', id: string, name?: string, chain: Chain, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', totalValueLocked?: { __typename?: 'Amount', value: number, currency?: Currency }, price?: { __typename?: 'Amount', value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', currency?: Currency, value: number }, volume?: { __typename?: 'Amount', value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', logoUrl?: string } }> };
|
||||
export type TopTokens100Query = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', id: string, name?: string, chain: Chain, address?: string, symbol?: string, standard?: TokenStandard, market?: { __typename?: 'TokenMarket', id: string, totalValueLocked?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, price?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', id: string, currency?: Currency, value: number }, volume?: { __typename?: 'Amount', id: string, value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', id: string, logoUrl?: string } }> };
|
||||
|
||||
export type TopTokensSparklineQueryVariables = Exact<{
|
||||
duration: HistoryDuration;
|
||||
@@ -854,7 +1061,14 @@ export type TopTokensSparklineQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type TopTokensSparklineQuery = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', address?: string, market?: { __typename?: 'TokenMarket', priceHistory?: Array<{ __typename?: 'TimestampedAmount', timestamp: number, value: number }> } }> };
|
||||
export type TopTokensSparklineQuery = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', id: string, address?: string, chain: Chain, market?: { __typename?: 'TokenMarket', id: string, priceHistory?: Array<{ __typename?: 'TimestampedAmount', id: string, timestamp: number, value: number }> } }> };
|
||||
|
||||
export type TrendingTokensQueryVariables = Exact<{
|
||||
chain: Chain;
|
||||
}>;
|
||||
|
||||
|
||||
export type TrendingTokensQuery = { __typename?: 'Query', topTokens?: Array<{ __typename?: 'Token', id: string, decimals?: number, name?: string, chain: Chain, standard?: TokenStandard, address?: string, symbol?: string, market?: { __typename?: 'TokenMarket', id: string, price?: { __typename?: 'Amount', id: string, value: number, currency?: Currency }, pricePercentChange?: { __typename?: 'Amount', id: string, value: number }, volume24H?: { __typename?: 'Amount', id: string, value: number, currency?: Currency } }, project?: { __typename?: 'TokenProject', id: string, logoUrl?: string, safetyLevel?: SafetyLevel } }> };
|
||||
|
||||
export type AssetQueryVariables = Exact<{
|
||||
address: Scalars['String'];
|
||||
@@ -895,44 +1109,220 @@ export type NftBalanceQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NftBalanceQuery = { __typename?: 'Query', nftBalances?: { __typename?: 'NftBalanceConnection', edges: Array<{ __typename?: 'NftBalanceEdge', node: { __typename?: 'NftBalance', listedMarketplaces?: Array<NftMarketplace>, ownedAsset?: { __typename?: 'NftAsset', id: string, animationUrl?: string, description?: string, flaggedBy?: string, name?: string, ownerAddress?: string, suspiciousFlag?: boolean, tokenId: string, collection?: { __typename?: 'NftCollection', isVerified?: boolean, name?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', value: number } }> }, image?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, thumbnail?: { __typename?: 'Image', url: string }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', node: { __typename?: 'NftOrder', createdAt: number, marketplace: NftMarketplace, endAt?: number, price: { __typename?: 'Amount', value: number, currency?: Currency } } }> } }, listingFees?: Array<{ __typename?: 'NftFee', payoutAddress: string, basisPoints: number }>, lastPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, timestamp: number, value: number } } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
|
||||
export type NftBalanceQuery = { __typename?: 'Query', nftBalances?: { __typename?: 'NftBalanceConnection', edges: Array<{ __typename?: 'NftBalanceEdge', node: { __typename?: 'NftBalance', listedMarketplaces?: Array<NftMarketplace>, ownedAsset?: { __typename?: 'NftAsset', id: string, animationUrl?: string, description?: string, flaggedBy?: string, name?: string, ownerAddress?: string, suspiciousFlag?: boolean, tokenId: string, collection?: { __typename?: 'NftCollection', isVerified?: boolean, name?: string, twitterName?: string, image?: { __typename?: 'Image', url: string }, nftContracts?: Array<{ __typename?: 'NftContract', address: string, chain: Chain, name?: string, standard?: NftStandard, symbol?: string, totalSupply?: number }>, markets?: Array<{ __typename?: 'NftCollectionMarket', floorPrice?: { __typename?: 'TimestampedAmount', value: number } }> }, image?: { __typename?: 'Image', url: string }, originalImage?: { __typename?: 'Image', url: string }, smallImage?: { __typename?: 'Image', url: string }, thumbnail?: { __typename?: 'Image', url: string }, listings?: { __typename?: 'NftOrderConnection', edges: Array<{ __typename?: 'NftOrderEdge', node: { __typename?: 'NftOrder', createdAt: number, marketplace: NftMarketplace, endAt?: number, price: { __typename?: 'Amount', value: number, currency?: Currency } } }> } }, listingFees?: Array<{ __typename?: 'NftFee', payoutAddress: string, basisPoints: number }>, lastPrice?: { __typename?: 'TimestampedAmount', currency?: Currency, timestamp: number, value: number } } }>, pageInfo: { __typename?: 'PageInfo', endCursor?: string, hasNextPage?: boolean, hasPreviousPage?: boolean, startCursor?: string } } };
|
||||
|
||||
export type NftRouteQueryVariables = Exact<{
|
||||
chain?: InputMaybe<Chain>;
|
||||
senderAddress: Scalars['String'];
|
||||
nftTrades: Array<NftTradeInput> | NftTradeInput;
|
||||
tokenTrades?: InputMaybe<Array<TokenTradeInput> | TokenTradeInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type NftRouteQuery = { __typename?: 'Query', nftRoute?: { __typename?: 'NftRouteResponse', id: string, calldata: string, toAddress: string, route?: Array<{ __typename?: 'NftTrade', amount: number, contractAddress: string, id: string, marketplace: NftMarketplace, tokenId: string, tokenType: NftStandard, price: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string }, quotePrice?: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string } }>, sendAmount: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string } } };
|
||||
|
||||
export type TrendingCollectionsQueryVariables = Exact<{
|
||||
size?: InputMaybe<Scalars['Int']>;
|
||||
timePeriod?: InputMaybe<HistoryDuration>;
|
||||
}>;
|
||||
|
||||
|
||||
export type TrendingCollectionsQuery = { __typename?: 'Query', topCollections?: { __typename?: 'NftCollectionConnection', edges: Array<{ __typename?: 'NftCollectionEdge', node: { __typename?: 'NftCollection', name?: string, isVerified?: boolean, nftContracts?: Array<{ __typename?: 'NftContract', address: string, totalSupply?: number }>, image?: { __typename?: 'Image', url: string }, bannerImage?: { __typename?: 'Image', url: string }, markets?: Array<{ __typename?: 'NftCollectionMarket', owners?: number, floorPrice?: { __typename?: 'TimestampedAmount', value: number }, totalVolume?: { __typename?: 'TimestampedAmount', value: number }, volume?: { __typename?: 'TimestampedAmount', value: number }, volumePercentChange?: { __typename?: 'TimestampedAmount', value: number }, floorPricePercentChange?: { __typename?: 'TimestampedAmount', value: number }, sales?: { __typename?: 'TimestampedAmount', value: number }, listings?: { __typename?: 'TimestampedAmount', value: number } }> } }> } };
|
||||
|
||||
|
||||
export const RecentlySearchedAssetsDocument = gql`
|
||||
query RecentlySearchedAssets($collectionAddresses: [String!]!, $contracts: [ContractInput!]!) {
|
||||
nftCollections(filter: {addresses: $collectionAddresses}) {
|
||||
edges {
|
||||
node {
|
||||
collectionId
|
||||
image {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
name
|
||||
numAssets
|
||||
nftContracts {
|
||||
address
|
||||
}
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens(contracts: $contracts) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useRecentlySearchedAssetsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useRecentlySearchedAssetsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useRecentlySearchedAssetsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useRecentlySearchedAssetsQuery({
|
||||
* variables: {
|
||||
* collectionAddresses: // value for 'collectionAddresses'
|
||||
* contracts: // value for 'contracts'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRecentlySearchedAssetsQuery(baseOptions: Apollo.QueryHookOptions<RecentlySearchedAssetsQuery, RecentlySearchedAssetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<RecentlySearchedAssetsQuery, RecentlySearchedAssetsQueryVariables>(RecentlySearchedAssetsDocument, options);
|
||||
}
|
||||
export function useRecentlySearchedAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<RecentlySearchedAssetsQuery, RecentlySearchedAssetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<RecentlySearchedAssetsQuery, RecentlySearchedAssetsQueryVariables>(RecentlySearchedAssetsDocument, options);
|
||||
}
|
||||
export type RecentlySearchedAssetsQueryHookResult = ReturnType<typeof useRecentlySearchedAssetsQuery>;
|
||||
export type RecentlySearchedAssetsLazyQueryHookResult = ReturnType<typeof useRecentlySearchedAssetsLazyQuery>;
|
||||
export type RecentlySearchedAssetsQueryResult = Apollo.QueryResult<RecentlySearchedAssetsQuery, RecentlySearchedAssetsQueryVariables>;
|
||||
export const SearchTokensDocument = gql`
|
||||
query SearchTokens($searchQuery: String!) {
|
||||
searchTokens(searchQuery: $searchQuery) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSearchTokensQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSearchTokensQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSearchTokensQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSearchTokensQuery({
|
||||
* variables: {
|
||||
* searchQuery: // value for 'searchQuery'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSearchTokensQuery(baseOptions: Apollo.QueryHookOptions<SearchTokensQuery, SearchTokensQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<SearchTokensQuery, SearchTokensQueryVariables>(SearchTokensDocument, options);
|
||||
}
|
||||
export function useSearchTokensLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SearchTokensQuery, SearchTokensQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<SearchTokensQuery, SearchTokensQueryVariables>(SearchTokensDocument, options);
|
||||
}
|
||||
export type SearchTokensQueryHookResult = ReturnType<typeof useSearchTokensQuery>;
|
||||
export type SearchTokensLazyQueryHookResult = ReturnType<typeof useSearchTokensLazyQuery>;
|
||||
export type SearchTokensQueryResult = Apollo.QueryResult<SearchTokensQuery, SearchTokensQueryVariables>;
|
||||
export const TokenDocument = gql`
|
||||
query Token($contract: ContractInput!) {
|
||||
tokens(contracts: [$contract]) {
|
||||
query Token($chain: Chain!, $address: String = null) {
|
||||
token(chain: $chain, address: $address) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
address
|
||||
symbol
|
||||
standard
|
||||
market(currency: USD) {
|
||||
id
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
|
||||
id
|
||||
value
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
description
|
||||
homepageUrl
|
||||
twitterName
|
||||
logoUrl
|
||||
tokens {
|
||||
id
|
||||
chain
|
||||
address
|
||||
}
|
||||
@@ -953,7 +1343,8 @@ export const TokenDocument = gql`
|
||||
* @example
|
||||
* const { data, loading, error } = useTokenQuery({
|
||||
* variables: {
|
||||
* contract: // value for 'contract'
|
||||
* chain: // value for 'chain'
|
||||
* address: // value for 'address'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -969,13 +1360,19 @@ export type TokenQueryHookResult = ReturnType<typeof useTokenQuery>;
|
||||
export type TokenLazyQueryHookResult = ReturnType<typeof useTokenLazyQuery>;
|
||||
export type TokenQueryResult = Apollo.QueryResult<TokenQuery, TokenQueryVariables>;
|
||||
export const TokenPriceDocument = gql`
|
||||
query TokenPrice($contract: ContractInput!, $duration: HistoryDuration!) {
|
||||
tokens(contracts: [$contract]) {
|
||||
query TokenPrice($chain: Chain!, $address: String = null, $duration: HistoryDuration!) {
|
||||
token(chain: $chain, address: $address) {
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
}
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
@@ -996,7 +1393,8 @@ export const TokenPriceDocument = gql`
|
||||
* @example
|
||||
* const { data, loading, error } = useTokenPriceQuery({
|
||||
* variables: {
|
||||
* contract: // value for 'contract'
|
||||
* chain: // value for 'chain'
|
||||
* address: // value for 'address'
|
||||
* duration: // value for 'duration'
|
||||
* },
|
||||
* });
|
||||
@@ -1020,25 +1418,32 @@ export const TopTokens100Document = gql`
|
||||
chain
|
||||
address
|
||||
symbol
|
||||
standard
|
||||
market(currency: USD) {
|
||||
id
|
||||
totalValueLocked {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: $duration) {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
volume(duration: $duration) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
@@ -1076,9 +1481,13 @@ export type TopTokens100QueryResult = Apollo.QueryResult<TopTokens100Query, TopT
|
||||
export const TopTokensSparklineDocument = gql`
|
||||
query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) {
|
||||
topTokens(pageSize: 100, page: 1, chain: $chain) {
|
||||
id
|
||||
address
|
||||
chain
|
||||
market(currency: USD) {
|
||||
id
|
||||
priceHistory(duration: $duration) {
|
||||
id
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
@@ -1115,6 +1524,69 @@ export function useTopTokensSparklineLazyQuery(baseOptions?: Apollo.LazyQueryHoo
|
||||
export type TopTokensSparklineQueryHookResult = ReturnType<typeof useTopTokensSparklineQuery>;
|
||||
export type TopTokensSparklineLazyQueryHookResult = ReturnType<typeof useTopTokensSparklineLazyQuery>;
|
||||
export type TopTokensSparklineQueryResult = Apollo.QueryResult<TopTokensSparklineQuery, TopTokensSparklineQueryVariables>;
|
||||
export const TrendingTokensDocument = gql`
|
||||
query TrendingTokens($chain: Chain!) {
|
||||
topTokens(pageSize: 4, page: 1, chain: $chain, orderBy: VOLUME) {
|
||||
id
|
||||
decimals
|
||||
name
|
||||
chain
|
||||
standard
|
||||
address
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
id
|
||||
price {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: DAY) {
|
||||
id
|
||||
value
|
||||
}
|
||||
volume24H: volume(duration: DAY) {
|
||||
id
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
id
|
||||
logoUrl
|
||||
safetyLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useTrendingTokensQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useTrendingTokensQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useTrendingTokensQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useTrendingTokensQuery({
|
||||
* variables: {
|
||||
* chain: // value for 'chain'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useTrendingTokensQuery(baseOptions: Apollo.QueryHookOptions<TrendingTokensQuery, TrendingTokensQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<TrendingTokensQuery, TrendingTokensQueryVariables>(TrendingTokensDocument, options);
|
||||
}
|
||||
export function useTrendingTokensLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TrendingTokensQuery, TrendingTokensQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<TrendingTokensQuery, TrendingTokensQueryVariables>(TrendingTokensDocument, options);
|
||||
}
|
||||
export type TrendingTokensQueryHookResult = ReturnType<typeof useTrendingTokensQuery>;
|
||||
export type TrendingTokensLazyQueryHookResult = ReturnType<typeof useTrendingTokensLazyQuery>;
|
||||
export type TrendingTokensQueryResult = Apollo.QueryResult<TrendingTokensQuery, TrendingTokensQueryVariables>;
|
||||
export const AssetDocument = gql`
|
||||
query Asset($address: String!, $orderBy: NftAssetSortableField, $asc: Boolean, $filter: NftAssetsFilterInput, $first: Int, $after: String, $last: Int, $before: String) {
|
||||
nftAssets(
|
||||
@@ -1487,6 +1959,7 @@ export const NftBalanceDocument = gql`
|
||||
url
|
||||
}
|
||||
name
|
||||
twitterName
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
@@ -1586,4 +2059,150 @@ export function useNftBalanceLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions
|
||||
}
|
||||
export type NftBalanceQueryHookResult = ReturnType<typeof useNftBalanceQuery>;
|
||||
export type NftBalanceLazyQueryHookResult = ReturnType<typeof useNftBalanceLazyQuery>;
|
||||
export type NftBalanceQueryResult = Apollo.QueryResult<NftBalanceQuery, NftBalanceQueryVariables>;
|
||||
export type NftBalanceQueryResult = Apollo.QueryResult<NftBalanceQuery, NftBalanceQueryVariables>;
|
||||
export const NftRouteDocument = gql`
|
||||
query NftRoute($chain: Chain = ETHEREUM, $senderAddress: String!, $nftTrades: [NftTradeInput!]!, $tokenTrades: [TokenTradeInput!]) {
|
||||
nftRoute(
|
||||
chain: $chain
|
||||
senderAddress: $senderAddress
|
||||
nftTrades: $nftTrades
|
||||
tokenTrades: $tokenTrades
|
||||
) {
|
||||
id
|
||||
calldata
|
||||
route {
|
||||
amount
|
||||
contractAddress
|
||||
id
|
||||
marketplace
|
||||
price {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
quotePrice {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
tokenId
|
||||
tokenType
|
||||
}
|
||||
sendAmount {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
toAddress
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useNftRouteQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useNftRouteQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useNftRouteQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useNftRouteQuery({
|
||||
* variables: {
|
||||
* chain: // value for 'chain'
|
||||
* senderAddress: // value for 'senderAddress'
|
||||
* nftTrades: // value for 'nftTrades'
|
||||
* tokenTrades: // value for 'tokenTrades'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useNftRouteQuery(baseOptions: Apollo.QueryHookOptions<NftRouteQuery, NftRouteQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<NftRouteQuery, NftRouteQueryVariables>(NftRouteDocument, options);
|
||||
}
|
||||
export function useNftRouteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NftRouteQuery, NftRouteQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<NftRouteQuery, NftRouteQueryVariables>(NftRouteDocument, options);
|
||||
}
|
||||
export type NftRouteQueryHookResult = ReturnType<typeof useNftRouteQuery>;
|
||||
export type NftRouteLazyQueryHookResult = ReturnType<typeof useNftRouteLazyQuery>;
|
||||
export type NftRouteQueryResult = Apollo.QueryResult<NftRouteQuery, NftRouteQueryVariables>;
|
||||
export const TrendingCollectionsDocument = gql`
|
||||
query TrendingCollections($size: Int, $timePeriod: HistoryDuration) {
|
||||
topCollections(limit: $size, duration: $timePeriod) {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
nftContracts {
|
||||
address
|
||||
totalSupply
|
||||
}
|
||||
image {
|
||||
url
|
||||
}
|
||||
bannerImage {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
value
|
||||
}
|
||||
owners
|
||||
totalVolume {
|
||||
value
|
||||
}
|
||||
volume(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
volumePercentChange(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
floorPricePercentChange(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
sales {
|
||||
value
|
||||
}
|
||||
totalVolume {
|
||||
value
|
||||
}
|
||||
listings {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useTrendingCollectionsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useTrendingCollectionsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useTrendingCollectionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useTrendingCollectionsQuery({
|
||||
* variables: {
|
||||
* size: // value for 'size'
|
||||
* timePeriod: // value for 'timePeriod'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useTrendingCollectionsQuery(baseOptions?: Apollo.QueryHookOptions<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>(TrendingCollectionsDocument, options);
|
||||
}
|
||||
export function useTrendingCollectionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>(TrendingCollectionsDocument, options);
|
||||
}
|
||||
export type TrendingCollectionsQueryHookResult = ReturnType<typeof useTrendingCollectionsQuery>;
|
||||
export type TrendingCollectionsLazyQueryHookResult = ReturnType<typeof useTrendingCollectionsLazyQuery>;
|
||||
export type TrendingCollectionsQueryResult = Apollo.QueryResult<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApolloClient, InMemoryCache } from '@apollo/client'
|
||||
import { relayStylePagination } from '@apollo/client/utilities'
|
||||
import { Reference, relayStylePagination } from '@apollo/client/utilities'
|
||||
|
||||
const GRAPHQL_URL = process.env.REACT_APP_AWS_API_ENDPOINT
|
||||
if (!GRAPHQL_URL) {
|
||||
@@ -7,6 +7,7 @@ if (!GRAPHQL_URL) {
|
||||
}
|
||||
|
||||
export const apolloClient = new ApolloClient({
|
||||
connectToDevTools: true,
|
||||
uri: GRAPHQL_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -18,6 +19,33 @@ export const apolloClient = new ApolloClient({
|
||||
fields: {
|
||||
nftBalances: relayStylePagination(),
|
||||
nftAssets: relayStylePagination(),
|
||||
// tell apollo client how to reference Token items in the cache after being fetched by queries that return Token[]
|
||||
token: {
|
||||
read(_, { args, toReference }): Reference | undefined {
|
||||
return toReference({
|
||||
__typename: 'Token',
|
||||
chain: args?.chain,
|
||||
address: args?.address,
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Token: {
|
||||
// key by chain, address combination so that Token(chain, address) endpoint can read from cache
|
||||
/**
|
||||
* NOTE: In any query for `token` or `tokens`, you must include the `chain` and `address` fields
|
||||
* in order for result to normalize properly in the cache.
|
||||
*/
|
||||
keyFields: ['chain', 'address'],
|
||||
fields: {
|
||||
address: {
|
||||
read(address: string | null): string | null {
|
||||
// backend endpoint sometimes returns checksummed, sometimes lowercased addresses
|
||||
// always use lowercased addresses in our app for consistency
|
||||
return address?.toLowerCase() ?? null
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@ gql`
|
||||
url
|
||||
}
|
||||
name
|
||||
twitterName
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
@@ -166,7 +167,12 @@ export function useNftBalance(
|
||||
image_url: asset?.collection?.image?.url,
|
||||
payout_address: queryAsset?.node?.listingFees?.[0]?.payoutAddress,
|
||||
},
|
||||
collection: asset?.collection as unknown as GenieCollection,
|
||||
collection: {
|
||||
name: asset.collection?.name,
|
||||
isVerified: asset.collection?.isVerified,
|
||||
imageUrl: asset.collection?.image?.url,
|
||||
twitterUrl: asset.collection?.twitterName ? `@${asset.collection?.twitterName}` : undefined,
|
||||
} as GenieCollection,
|
||||
collectionIsVerified: asset?.collection?.isVerified,
|
||||
lastPrice: queryAsset.node.lastPrice?.value,
|
||||
floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value,
|
||||
|
||||
39
src/graphql/data/nft/Routing.ts
Normal file
39
src/graphql/data/nft/Routing.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
gql`
|
||||
query NftRoute(
|
||||
$chain: Chain = ETHEREUM
|
||||
$senderAddress: String!
|
||||
$nftTrades: [NftTradeInput!]!
|
||||
$tokenTrades: [TokenTradeInput!]
|
||||
) {
|
||||
nftRoute(chain: $chain, senderAddress: $senderAddress, nftTrades: $nftTrades, tokenTrades: $tokenTrades) {
|
||||
id
|
||||
calldata
|
||||
route {
|
||||
amount
|
||||
contractAddress
|
||||
id
|
||||
marketplace
|
||||
price {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
quotePrice {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
tokenId
|
||||
tokenType
|
||||
}
|
||||
sendAmount {
|
||||
id
|
||||
currency
|
||||
value
|
||||
}
|
||||
toAddress
|
||||
}
|
||||
}
|
||||
`
|
||||
95
src/graphql/data/nft/TrendingCollections.ts
Normal file
95
src/graphql/data/nft/TrendingCollections.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
|
||||
import gql from 'graphql-tag'
|
||||
import { TrendingCollection } from 'nft/types'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { HistoryDuration, useTrendingCollectionsQuery } from '../__generated__/types-and-hooks'
|
||||
|
||||
gql`
|
||||
query TrendingCollections($size: Int, $timePeriod: HistoryDuration) {
|
||||
topCollections(limit: $size, duration: $timePeriod) {
|
||||
edges {
|
||||
node {
|
||||
name
|
||||
nftContracts {
|
||||
address
|
||||
totalSupply
|
||||
}
|
||||
image {
|
||||
url
|
||||
}
|
||||
bannerImage {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
value
|
||||
}
|
||||
owners
|
||||
totalVolume {
|
||||
value
|
||||
}
|
||||
volume(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
volumePercentChange(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
floorPricePercentChange(duration: $timePeriod) {
|
||||
value
|
||||
}
|
||||
sales {
|
||||
value
|
||||
}
|
||||
listings {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function useTrendingCollections(size: number, timePeriod: HistoryDuration) {
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
const { data, loading, error } = useTrendingCollectionsQuery({
|
||||
variables: {
|
||||
size,
|
||||
timePeriod,
|
||||
},
|
||||
skip: !isNftGraphqlEnabled,
|
||||
})
|
||||
|
||||
const trendingCollections: TrendingCollection[] | undefined = useMemo(
|
||||
() =>
|
||||
data?.topCollections?.edges?.map((edge) => {
|
||||
const collection = edge?.node
|
||||
return {
|
||||
name: collection.name,
|
||||
address: collection.nftContracts?.[0].address,
|
||||
imageUrl: collection.image?.url,
|
||||
bannerImageUrl: collection.bannerImage?.url,
|
||||
isVerified: collection.isVerified,
|
||||
volume: collection.markets?.[0].volume?.value,
|
||||
volumeChange: collection.markets?.[0].volumePercentChange?.value,
|
||||
floor: collection.markets?.[0].floorPrice?.value,
|
||||
floorChange: collection.markets?.[0].floorPricePercentChange?.value,
|
||||
marketCap: collection.markets?.[0].totalVolume?.value,
|
||||
percentListed:
|
||||
(collection.markets?.[0].listings?.value ?? 0) / (collection.nftContracts?.[0].totalSupply ?? 1),
|
||||
owners: collection.markets?.[0].owners,
|
||||
sales: collection.markets?.[0].sales?.value,
|
||||
totalSupply: collection.nftContracts?.[0].totalSupply,
|
||||
}
|
||||
}),
|
||||
[data?.topCollections?.edges]
|
||||
)
|
||||
|
||||
return {
|
||||
data: trendingCollections,
|
||||
loading,
|
||||
error,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,30 @@
|
||||
import { QueryResult } from '@apollo/client'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { ZERO_ADDRESS } from 'constants/misc'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { Chain, HistoryDuration } from './__generated__/types-and-hooks'
|
||||
|
||||
export enum PollingInterval {
|
||||
Slow = ms`5m`,
|
||||
Normal = ms`1m`,
|
||||
Fast = ms`12s`, // 12 seconds, block times for mainnet
|
||||
LightningMcQueen = ms`3s`, // 3 seconds, approx block times for polygon
|
||||
}
|
||||
|
||||
// Polls a query only when the current component is mounted, as useQuery's pollInterval prop will continue to poll after unmount
|
||||
export function usePollQueryWhileMounted<T, K>(queryResult: QueryResult<T, K>, interval: PollingInterval) {
|
||||
const { startPolling, stopPolling } = queryResult
|
||||
|
||||
useEffect(() => {
|
||||
startPolling(interval)
|
||||
return stopPolling
|
||||
}, [interval, startPolling, stopPolling])
|
||||
|
||||
return queryResult
|
||||
}
|
||||
|
||||
export enum TimePeriod {
|
||||
HOUR,
|
||||
DAY,
|
||||
@@ -74,17 +95,8 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = {
|
||||
|
||||
export const BACKEND_CHAIN_NAMES: Chain[] = [Chain.Ethereum, Chain.Polygon, Chain.Optimism, Chain.Arbitrum, Chain.Celo]
|
||||
|
||||
export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?: number) {
|
||||
if (address === ZERO_ADDRESS && chainId && chainId === SupportedChainId.MAINNET) {
|
||||
return `/tokens/${CHAIN_ID_TO_BACKEND_NAME[chainId].toLowerCase()}/${NATIVE_CHAIN_ID}`
|
||||
} else if (chainName) {
|
||||
return `/tokens/${chainName.toLowerCase()}/${address}`
|
||||
} else if (chainId) {
|
||||
const chainName = CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
return chainName ? `/tokens/${chainName.toLowerCase()}/${address}` : ''
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
export function getTokenDetailsURL({ address, chain }: { address?: string | null; chain: Chain }) {
|
||||
return `/tokens/${chain.toLowerCase()}/${address ?? NATIVE_CHAIN_ID}`
|
||||
}
|
||||
|
||||
export function unwrapToken<
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function useAllV3TicksQuery(poolAddress: string | undefined, skip
|
||||
data,
|
||||
loading: isLoading,
|
||||
error,
|
||||
} = useQuery(query, {
|
||||
} = useQuery<Record<'ticks', Ticks>>(query, {
|
||||
variables: {
|
||||
poolAddress: poolAddress?.toLowerCase(),
|
||||
skip,
|
||||
|
||||
680
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
680
src/graphql/thegraph/__generated__/types-and-hooks.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -112,7 +112,7 @@ export function useSearchInactiveTokenLists(search: string | undefined, minResul
|
||||
const result: WrappedTokenInfo[] = []
|
||||
const addressSet: { [address: string]: true } = {}
|
||||
for (const url of inactiveUrls) {
|
||||
const list = lists[url].current
|
||||
const list = lists[url]?.current
|
||||
if (!list) continue
|
||||
for (const tokenInfo of list.tokens) {
|
||||
if (tokenInfo.chainId === chainId && tokenFilter(tokenInfo)) {
|
||||
|
||||
@@ -9,30 +9,25 @@ import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
import { fetchTokenList } from '../state/lists/actions'
|
||||
|
||||
export function useFetchListCallback(): (
|
||||
listUrl: string,
|
||||
sendDispatch?: boolean,
|
||||
skipValidation?: boolean
|
||||
) => Promise<TokenList> {
|
||||
export function useFetchListCallback(): (listUrl: string, skipValidation?: boolean) => Promise<TokenList> {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// note: prevent dispatch if using for list search or unsupported list
|
||||
return useCallback(
|
||||
async (listUrl: string, sendDispatch = true, skipValidation?: boolean) => {
|
||||
async (listUrl: string, skipValidation?: boolean) => {
|
||||
const requestId = nanoid()
|
||||
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
|
||||
dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
|
||||
return getTokenList(
|
||||
listUrl,
|
||||
(ensName: string) => resolveENSContentHash(ensName, RPC_PROVIDERS[SupportedChainId.MAINNET]),
|
||||
skipValidation
|
||||
)
|
||||
.then((tokenList) => {
|
||||
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
|
||||
dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
|
||||
return tokenList
|
||||
})
|
||||
.catch((error) => {
|
||||
console.debug(`Failed to get list at url ${listUrl}`, error)
|
||||
sendDispatch && dispatch(fetchTokenList.rejected({ url: listUrl, requestId, errorMessage: error.message }))
|
||||
dispatch(fetchTokenList.rejected({ url: listUrl, requestId, errorMessage: error.message }))
|
||||
throw error
|
||||
})
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@ interface AllowanceRequired {
|
||||
approveAndPermit: () => Promise<void>
|
||||
}
|
||||
|
||||
type Allowance =
|
||||
export type Allowance =
|
||||
| { state: AllowanceState.LOADING }
|
||||
| {
|
||||
state: AllowanceState.ALLOWED
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'utils/signTypedData'
|
||||
|
||||
import { signTypedData } from '@uniswap/conedison/provider'
|
||||
import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
@@ -77,7 +76,8 @@ export function useUpdatePermitAllowance(
|
||||
}
|
||||
|
||||
const { domain, types, values } = AllowanceTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)
|
||||
const signature = await provider.getSigner(account)._signTypedData(domain, types, values)
|
||||
// Use conedison's signTypedData for better x-wallet compatibility.
|
||||
const signature = await signTypedData(provider.getSigner(account), domain, types, values)
|
||||
onPermitSignature?.({ ...permit, signature })
|
||||
return
|
||||
} catch (e: unknown) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FeeAmount, nearestUsableTick, Pool, TICK_SPACINGS, tickToPrice } from '
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { ZERO_ADDRESS } from 'constants/misc'
|
||||
import useAllV3TicksQuery, { TickData } from 'graphql/thegraph/AllV3TicksQuery'
|
||||
import useAllV3TicksQuery, { TickData, Ticks } from 'graphql/thegraph/AllV3TicksQuery'
|
||||
import JSBI from 'jsbi'
|
||||
import { useSingleContractMultipleData } from 'lib/hooks/multicall'
|
||||
import ms from 'ms.macro'
|
||||
@@ -140,7 +140,8 @@ function useTicksFromTickLens(
|
||||
function useTicksFromSubgraph(
|
||||
currencyA: Currency | undefined,
|
||||
currencyB: Currency | undefined,
|
||||
feeAmount: FeeAmount | undefined
|
||||
feeAmount: FeeAmount | undefined,
|
||||
skip = 0
|
||||
) {
|
||||
const { chainId } = useWeb3React()
|
||||
const poolAddress =
|
||||
@@ -154,9 +155,10 @@ function useTicksFromSubgraph(
|
||||
)
|
||||
: undefined
|
||||
|
||||
return useAllV3TicksQuery(poolAddress, 0, ms`30s`)
|
||||
return useAllV3TicksQuery(poolAddress, skip, ms`30s`)
|
||||
}
|
||||
|
||||
const MAX_THE_GRAPH_TICK_FETCH_VALUE = 1000
|
||||
// Fetches all ticks for a given pool
|
||||
function useAllV3Ticks(
|
||||
currencyA: Currency | undefined,
|
||||
@@ -170,12 +172,31 @@ function useAllV3Ticks(
|
||||
const useSubgraph = currencyA ? !CHAIN_IDS_MISSING_SUBGRAPH_DATA.includes(currencyA.chainId) : true
|
||||
|
||||
const tickLensTickData = useTicksFromTickLens(!useSubgraph ? currencyA : undefined, currencyB, feeAmount)
|
||||
const subgraphTickData = useTicksFromSubgraph(useSubgraph ? currencyA : undefined, currencyB, feeAmount)
|
||||
|
||||
const [skipNumber, setSkipNumber] = useState(0)
|
||||
const [subgraphTickData, setSubgraphTickData] = useState<Ticks>([])
|
||||
const { data, error, isLoading } = useTicksFromSubgraph(
|
||||
useSubgraph ? currencyA : undefined,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
skipNumber
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.ticks.length) {
|
||||
setSubgraphTickData((tickData) => [...tickData, ...data.ticks])
|
||||
if (data.ticks.length === MAX_THE_GRAPH_TICK_FETCH_VALUE) {
|
||||
setSkipNumber((skipNumber) => skipNumber + MAX_THE_GRAPH_TICK_FETCH_VALUE)
|
||||
}
|
||||
}
|
||||
}, [data?.ticks])
|
||||
|
||||
return {
|
||||
isLoading: useSubgraph ? subgraphTickData.isLoading : tickLensTickData.isLoading,
|
||||
error: useSubgraph ? subgraphTickData.error : tickLensTickData.isError,
|
||||
ticks: useSubgraph ? subgraphTickData.data?.ticks : tickLensTickData.tickData,
|
||||
isLoading: useSubgraph
|
||||
? isLoading || data?.ticks.length === MAX_THE_GRAPH_TICK_FETCH_VALUE
|
||||
: tickLensTickData.isLoading,
|
||||
error: useSubgraph ? error : tickLensTickData.isError,
|
||||
ticks: useSubgraph ? subgraphTickData : tickLensTickData.tickData,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,12 @@ export default function useStablecoinPrice(currency?: Currency): Price<Currency,
|
||||
}, [currency, stablecoin, trade])
|
||||
|
||||
const lastPrice = useRef(price)
|
||||
if (!price || !lastPrice.current || !price.equalTo(lastPrice.current)) {
|
||||
if (
|
||||
!price ||
|
||||
!lastPrice.current ||
|
||||
!price.equalTo(lastPrice.current) ||
|
||||
!price.baseCurrency.equals(lastPrice.current.baseCurrency)
|
||||
) {
|
||||
lastPrice.current = price
|
||||
}
|
||||
return lastPrice.current
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { TransactionResponse } from '@ethersproject/abstract-provider'
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { FeeOptions, toHex } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
|
||||
import { useCallback } from 'react'
|
||||
import { calculateGasMargin } from 'utils/calculateGasMargin'
|
||||
import isZero from 'utils/isZero'
|
||||
@@ -12,6 +16,8 @@ import { swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMes
|
||||
|
||||
import { PermitSignature } from './usePermitAllowance'
|
||||
|
||||
class InvalidSwapError extends Error {}
|
||||
|
||||
interface SwapOptions {
|
||||
slippageTolerance: Percent
|
||||
deadline?: BigNumber
|
||||
@@ -50,16 +56,30 @@ export function useUniversalRouterSwapCallback(
|
||||
try {
|
||||
gasEstimate = await provider.estimateGas(tx)
|
||||
} catch (gasError) {
|
||||
await provider.call(tx) // this should throw the actual error
|
||||
// If the actual error is not thrown, just try again:
|
||||
gasEstimate = await provider.estimateGas(tx)
|
||||
console.warn(gasError)
|
||||
throw new Error('Your swap is expected to fail')
|
||||
}
|
||||
const gasLimit = calculateGasMargin(gasEstimate)
|
||||
const response = await provider.getSigner().sendTransaction({ ...tx, gasLimit })
|
||||
const response = await provider
|
||||
.getSigner()
|
||||
.sendTransaction({ ...tx, gasLimit })
|
||||
.then((response) => {
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_SIGNED,
|
||||
formatSwapSignedAnalyticsEventProperties({ trade, txHash: response.hash })
|
||||
)
|
||||
if (tx.data !== response.data) {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { txHash: response.hash })
|
||||
throw new InvalidSwapError(
|
||||
t`Your swap was modified through your wallet. If this was a mistake, please cancel immediately or risk losing your funds.`
|
||||
)
|
||||
}
|
||||
return response
|
||||
})
|
||||
return response
|
||||
} catch (swapError: unknown) {
|
||||
const message = swapErrorToUserReadableMessage(swapError)
|
||||
throw new Error(message)
|
||||
if (swapError instanceof InvalidSwapError) throw swapError
|
||||
throw new Error(swapErrorToUserReadableMessage(swapError))
|
||||
}
|
||||
}, [
|
||||
account,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
|
||||
/**
|
||||
* Organizes the number of Token and NFT results to be shown to a user depending on if they're in the NFT or Token experience
|
||||
@@ -10,9 +11,9 @@ import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
*/
|
||||
export function organizeSearchResults(
|
||||
isNFTPage: boolean,
|
||||
tokenResults: FungibleToken[],
|
||||
tokenResults: SearchToken[],
|
||||
collectionResults: GenieCollection[]
|
||||
): [FungibleToken[], GenieCollection[]] {
|
||||
): [SearchToken[], GenieCollection[]] {
|
||||
const reducedTokens =
|
||||
tokenResults?.slice(0, isNFTPage ? 3 : collectionResults.length < 3 ? 8 - collectionResults.length : 5) ?? []
|
||||
const reducedCollections = collectionResults.slice(0, 8 - reducedTokens.length)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user