Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
2
.env
2
.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"
|
||||
@@ -10,4 +11,3 @@ REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_MOONPAY_API="https://api.moonpay.com"
|
||||
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkStaging?platform=web"
|
||||
REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||
REACT_APP_STATSIG_API_KEY="client-1rY92WZGidd2hgW4x1lsZ7afqm1Qfr3sJfH3A5b8eJa"
|
||||
|
||||
@@ -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"
|
||||
@@ -9,4 +11,3 @@ REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
|
||||
REACT_APP_SENTRY_ENABLED=false
|
||||
REACT_APP_STATSIG_API_KEY="client-1rY92WZGidd2hgW4x1lsZ7afqm1Qfr3sJfH3A5b8eJa"
|
||||
|
||||
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 }}
|
||||
|
||||
|
||||
@@ -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,8 +133,8 @@
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@sentry/react": "^7.29.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.0",
|
||||
"@uniswap/analytics-events": "^2.3.0",
|
||||
"@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",
|
||||
@@ -152,7 +152,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.29.3",
|
||||
"@uniswap/widgets": "^2.40.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
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'
|
||||
@@ -212,12 +212,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.permit2}
|
||||
label="Permit 2 / Universal Router"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={NftListV2Variant}
|
||||
value={useNftListV2Flag()}
|
||||
featureFlag={FeatureFlag.nftListV2}
|
||||
label="NFT Listing Page v2"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={PayWithAnyTokenVariant}
|
||||
value={usePayWithAnyTokenFlag()}
|
||||
@@ -236,6 +230,12 @@ export default function FeatureFlagModal() {
|
||||
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}
|
||||
|
||||
@@ -10,8 +10,10 @@ 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;
|
||||
@@ -29,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;
|
||||
@@ -123,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>
|
||||
@@ -138,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,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'
|
||||
@@ -82,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 (
|
||||
@@ -124,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 />
|
||||
|
||||
@@ -105,7 +105,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
}
|
||||
|
||||
<div
|
||||
style="padding-right: 8px; padding-top: 8px;"
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
@@ -388,7 +388,7 @@ exports[`renders loading rows when isLoading is true 1`] = `
|
||||
}
|
||||
|
||||
<div
|
||||
style="padding-right: 8px; padding-top: 8px;"
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
|
||||
@@ -290,7 +290,7 @@ export default function CurrencyList({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ paddingRight: '8px', paddingTop: '8px' }}>
|
||||
<div style={{ paddingRight: '4px' }}>
|
||||
{isLoading ? (
|
||||
<FixedSizeList
|
||||
className={styles.scrollbarStyle}
|
||||
|
||||
@@ -3,11 +3,10 @@ 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 { FeatureGate } from 'featureFlags/flags/featureFlags'
|
||||
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'
|
||||
import { useGate } from 'statsig-react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { StyledInternalLink } from 'theme'
|
||||
|
||||
@@ -89,7 +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 { value: isDummyGateFlagEnabled } = useGate(FeatureGate.DUMMY)
|
||||
const isDummyGateFlagEnabled = useDummyGateEnabled()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function Widget({
|
||||
onDefaultTokenChange,
|
||||
onReviewSwapClick,
|
||||
}: WidgetProps) {
|
||||
const { connector, provider } = useWeb3React()
|
||||
const { connector, provider, chainId } = useWeb3React()
|
||||
const locale = useActiveLocale()
|
||||
const theme = useWidgetTheme()
|
||||
const { inputs, tokenSelector } = useSyncWidgetInputs({
|
||||
@@ -169,7 +169,7 @@ export default function Widget({
|
||||
locale={locale}
|
||||
theme={theme}
|
||||
width={width}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
defaultChainId={chainId}
|
||||
onConnectWalletClick={onConnectWalletClick}
|
||||
provider={provider}
|
||||
onSwitchChain={onSwitchChain}
|
||||
@@ -183,6 +183,9 @@ export default function Widget({
|
||||
onSwapApprove={onApproveToken}
|
||||
onInitialSwapQuote={onInitialSwapQuote}
|
||||
onSwapPriceUpdateAck={onSwapPriceUpdateAck}
|
||||
onError={(error, errorInfo) => {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_ERROR, { error, errorInfo, ...trace })
|
||||
}}
|
||||
/>
|
||||
{tokenSelector}
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 = ''
|
||||
@@ -29,24 +31,35 @@ export function useSyncWidgetInputs({
|
||||
}) {
|
||||
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>(defaultTokens)
|
||||
|
||||
useEffect(() => {
|
||||
if (!tokens[Field.INPUT] && !tokens[Field.OUTPUT]) {
|
||||
setTokens((tokens) => {
|
||||
const update = {
|
||||
...tokens,
|
||||
[Field.INPUT]: defaultTokens[Field.INPUT] ?? tokens[Field.INPUT],
|
||||
[Field.OUTPUT]: defaultTokens[Field.OUTPUT] ?? tokens[Field.OUTPUT] ?? defaultTokens.default,
|
||||
default: defaultTokens.default,
|
||||
}
|
||||
return update
|
||||
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') => {
|
||||
if (origin === 'max') {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const UNI_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
|
||||
export const UNI_EXTENDED_LIST = 'https://extendedtokens.uniswap.org/'
|
||||
const UNI_UNSUPPORTED_LIST = 'https://unsupportedtokens.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'
|
||||
|
||||
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,12 +1,12 @@
|
||||
/**
|
||||
* The value here must match the value in the statsig dashboard, if you plan to use statsig.
|
||||
*/
|
||||
export enum FeatureFlag {
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
permit2 = 'permit2',
|
||||
nftListV2 = 'nftListV2',
|
||||
payWithAnyToken = 'payWithAnyToken',
|
||||
swapWidget = 'swapWidget',
|
||||
swapWidget = 'swap_widget_replacement_enabled',
|
||||
gqlRouting = 'gqlRouting',
|
||||
}
|
||||
|
||||
export enum FeatureGate {
|
||||
DUMMY = 'web_dummy_gate_amplitude_id',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useGqlRoutingFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.gqlRouting)
|
||||
return useBaseFlag(FeatureFlag.gqlRouting, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export { BaseVariant as GqlRoutingVariant }
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useNftListV2Flag(): BaseVariant {
|
||||
return BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as NftListV2Variant }
|
||||
7
src/featureFlags/flags/nftlGraphql.ts
Normal file
7
src/featureFlags/flags/nftlGraphql.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useNftGraphqlFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.nftGraphql)
|
||||
}
|
||||
|
||||
export { BaseVariant as NftGraphqlVariant }
|
||||
@@ -4,7 +4,7 @@ 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 {
|
||||
|
||||
@@ -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':
|
||||
|
||||
86
src/graphql/data/__generated__/types-and-hooks.ts
generated
86
src/graphql/data/__generated__/types-and-hooks.ts
generated
@@ -86,6 +86,10 @@ export enum Chain {
|
||||
UnknownChain = 'UNKNOWN_CHAIN'
|
||||
}
|
||||
|
||||
export enum CollectionSortableField {
|
||||
Volume = 'VOLUME'
|
||||
}
|
||||
|
||||
export type ContractInput = {
|
||||
address?: InputMaybe<Scalars['String']>;
|
||||
chain: Chain;
|
||||
@@ -96,11 +100,6 @@ export enum Currency {
|
||||
Usd = 'USD'
|
||||
}
|
||||
|
||||
export enum DatasourceProvider {
|
||||
Alternate = 'ALTERNATE',
|
||||
Legacy = 'LEGACY'
|
||||
}
|
||||
|
||||
export type Dimensions = {
|
||||
__typename?: 'Dimensions';
|
||||
height?: Maybe<Scalars['Float']>;
|
||||
@@ -145,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'];
|
||||
@@ -196,7 +238,6 @@ export type NftAssetListingsArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
asc?: InputMaybe<Scalars['Boolean']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
datasource?: InputMaybe<DatasourceProvider>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
@@ -311,7 +352,6 @@ export type NftCollection = {
|
||||
|
||||
export type NftCollectionMarketsArgs = {
|
||||
currencies: Array<Currency>;
|
||||
datasource?: InputMaybe<DatasourceProvider>;
|
||||
};
|
||||
|
||||
export type NftCollectionConnection = {
|
||||
@@ -335,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>;
|
||||
@@ -602,6 +644,7 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = {
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
nftActivity?: Maybe<NftActivityConnection>;
|
||||
nftAssets?: Maybe<NftAssetConnection>;
|
||||
nftBalances?: Maybe<NftBalanceConnection>;
|
||||
nftCollections?: Maybe<NftCollectionConnection>;
|
||||
@@ -613,17 +656,25 @@ export type Query = {
|
||||
token?: Maybe<Token>;
|
||||
tokenProjects?: Maybe<Array<Maybe<TokenProject>>>;
|
||||
tokens?: Maybe<Array<Maybe<Token>>>;
|
||||
topCollections?: Maybe<NftCollectionConnection>;
|
||||
topTokens?: Maybe<Array<Maybe<Token>>>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryNftActivityArgs = {
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftActivityFilterInput>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryNftAssetsArgs = {
|
||||
address: Scalars['String'];
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
asc?: InputMaybe<Scalars['Boolean']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
datasource?: InputMaybe<DatasourceProvider>;
|
||||
filter?: InputMaybe<NftAssetsFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
@@ -636,7 +687,6 @@ export type QueryNftBalancesArgs = {
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
datasource?: InputMaybe<DatasourceProvider>;
|
||||
filter?: InputMaybe<NftBalancesFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
@@ -646,12 +696,10 @@ export type QueryNftBalancesArgs = {
|
||||
|
||||
|
||||
export type QueryNftCollectionsArgs = {
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
datasource?: InputMaybe<DatasourceProvider>;
|
||||
chain?: InputMaybe<Chain>;
|
||||
cursor?: InputMaybe<Scalars['String']>;
|
||||
filter?: InputMaybe<NftCollectionsFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']>;
|
||||
last?: InputMaybe<Scalars['Int']>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -670,7 +718,6 @@ export type QueryNftRouteArgs = {
|
||||
|
||||
export type QueryPortfoliosArgs = {
|
||||
ownerAddresses: Array<Scalars['String']>;
|
||||
useAltDataSource?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -700,6 +747,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>;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { NFTEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useIsNftDetailsPage, useIsNftPage, useIsNftProfilePage } from 'hooks/useIsNftPage'
|
||||
import { BagFooter } from 'nft/components/bag/BagFooter'
|
||||
import ListingModal from 'nft/components/bag/profile/ListingModal'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column } from 'nft/components/Flex'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { buttonTextMedium, commonButtonStyles } from 'nft/css/common.css'
|
||||
import {
|
||||
useBag,
|
||||
useIsMobile,
|
||||
@@ -66,7 +64,7 @@ const BagContainer = styled.div<{ raiseZIndex: boolean; isProfilePage: boolean }
|
||||
border-radius: 16px;
|
||||
box-shadow: ${({ theme }) => theme.shallowShadow};
|
||||
z-index: ${({ raiseZIndex, isProfilePage }) =>
|
||||
raiseZIndex ? (isProfilePage ? Z_INDEX.modalOverTooltip : Z_INDEX.modalBackdrop + 2) : 3};
|
||||
raiseZIndex ? (isProfilePage ? Z_INDEX.modalOverTooltip : Z_INDEX.modalBackdrop - 1) : 3};
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
right: 0px;
|
||||
@@ -90,6 +88,24 @@ const DetailsPageBackground = styled.div`
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContinueButton = styled.div`
|
||||
background: ${({ theme }) => theme.accentAction};
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
margin: 32px 28px 16px;
|
||||
padding: 10px 0px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
transition: ${({ theme }) => theme.transition.duration.medium};
|
||||
|
||||
:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
`
|
||||
|
||||
const ScrollingIndicator = ({ top, show }: SeparatorProps) => (
|
||||
<Box
|
||||
marginX="24"
|
||||
@@ -114,10 +130,7 @@ const Bag = () => {
|
||||
shallow
|
||||
)
|
||||
|
||||
const { profilePageState, setProfilePageState } = useProfilePageState(
|
||||
({ setProfilePageState, state }) => ({ profilePageState: state, setProfilePageState }),
|
||||
shallow
|
||||
)
|
||||
const { setProfilePageState } = useProfilePageState(({ setProfilePageState }) => ({ setProfilePageState }))
|
||||
|
||||
const {
|
||||
bagStatus,
|
||||
@@ -139,7 +152,6 @@ const Bag = () => {
|
||||
const isDetailsPage = useIsNftDetailsPage()
|
||||
const isNFTPage = useIsNftPage()
|
||||
const isMobile = useIsMobile()
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const usingGqlRouting = useGqlRoutingFlag() === GqlRoutingVariant.Enabled
|
||||
|
||||
const sendTransaction = useSendTransaction((state) => state.sendTransaction)
|
||||
@@ -398,48 +410,34 @@ const Bag = () => {
|
||||
return (
|
||||
<Portal>
|
||||
<BagContainer data-testid="nft-bag" raiseZIndex={isMobile || isModalOpen} isProfilePage={isProfilePage}>
|
||||
{!(isProfilePage && profilePageState === ProfilePageStateType.LISTING) ? (
|
||||
<>
|
||||
<BagHeader
|
||||
numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length}
|
||||
closeBag={handleCloseBag}
|
||||
resetFlow={isProfilePage ? resetSellAssets : reset}
|
||||
isProfilePage={isProfilePage}
|
||||
/>
|
||||
{shouldRenderEmptyState && <EmptyState />}
|
||||
<ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
|
||||
<Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
|
||||
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
|
||||
</Column>
|
||||
{hasAssetsToShow && !isProfilePage && (
|
||||
<BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} />
|
||||
)}
|
||||
{isSellingAssets && isProfilePage && (
|
||||
<Box
|
||||
marginTop="32"
|
||||
marginX="28"
|
||||
marginBottom="16"
|
||||
paddingY="10"
|
||||
className={`${buttonTextMedium} ${commonButtonStyles}`}
|
||||
backgroundColor="accentAction"
|
||||
color="white"
|
||||
textAlign="center"
|
||||
onClick={() => {
|
||||
;(isMobile || isNftListV2) && toggleBag()
|
||||
setProfilePageState(ProfilePageStateType.LISTING)
|
||||
sendAnalyticsEvent(NFTEventName.NFT_PROFILE_PAGE_START_SELL, {
|
||||
list_quantity: sellAssets.length,
|
||||
collection_addresses: sellAssets.map((asset) => asset.asset_contract.address),
|
||||
token_ids: sellAssets.map((asset) => asset.tokenId),
|
||||
})
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<ListingModal />
|
||||
<BagHeader
|
||||
numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length}
|
||||
closeBag={handleCloseBag}
|
||||
resetFlow={isProfilePage ? resetSellAssets : reset}
|
||||
isProfilePage={isProfilePage}
|
||||
/>
|
||||
{shouldRenderEmptyState && <EmptyState />}
|
||||
<ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
|
||||
<Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
|
||||
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
|
||||
</Column>
|
||||
{hasAssetsToShow && !isProfilePage && (
|
||||
<BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} />
|
||||
)}
|
||||
{isSellingAssets && isProfilePage && (
|
||||
<ContinueButton
|
||||
onClick={() => {
|
||||
toggleBag()
|
||||
setProfilePageState(ProfilePageStateType.LISTING)
|
||||
sendAnalyticsEvent(NFTEventName.NFT_PROFILE_PAGE_START_SELL, {
|
||||
list_quantity: sellAssets.length,
|
||||
collection_addresses: sellAssets.map((asset) => asset.asset_contract.address),
|
||||
token_ids: sellAssets.map((asset) => asset.tokenId),
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Trans>Continue</Trans>
|
||||
</ContinueButton>
|
||||
)}
|
||||
</BagContainer>
|
||||
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
import { Plural, t } from '@lingui/macro'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import ms from 'ms.macro'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { ArrowRightIcon, HazardIcon, LoadingIcon, XMarkIcon } from 'nft/components/icons'
|
||||
import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal'
|
||||
import { bodySmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useNFTList, useSellAsset } from 'nft/hooks'
|
||||
import { Listing, ListingStatus, WalletAsset } from 'nft/types'
|
||||
import { pluralize } from 'nft/utils/roundAndPluralize'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
import * as styles from './ListingModal.css'
|
||||
import { getListings } from './utils'
|
||||
|
||||
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
|
||||
|
||||
interface ListingButtonProps {
|
||||
onClick: () => void
|
||||
buttonText: string
|
||||
showWarningOverride?: boolean
|
||||
}
|
||||
|
||||
export const ListingButton = ({ onClick, buttonText, showWarningOverride = false }: ListingButtonProps) => {
|
||||
const {
|
||||
addMarketplaceWarning,
|
||||
sellAssets,
|
||||
removeAllMarketplaceWarnings,
|
||||
showResolveIssues,
|
||||
toggleShowResolveIssues,
|
||||
issues,
|
||||
setIssues,
|
||||
} = useSellAsset(
|
||||
({
|
||||
addMarketplaceWarning,
|
||||
sellAssets,
|
||||
removeAllMarketplaceWarnings,
|
||||
showResolveIssues,
|
||||
toggleShowResolveIssues,
|
||||
issues,
|
||||
setIssues,
|
||||
}) => ({
|
||||
addMarketplaceWarning,
|
||||
sellAssets,
|
||||
removeAllMarketplaceWarnings,
|
||||
showResolveIssues,
|
||||
toggleShowResolveIssues,
|
||||
issues,
|
||||
setIssues,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const { listingStatus, setListingStatus, setListings, setCollectionsRequiringApproval } = useNFTList(
|
||||
({ listingStatus, setListingStatus, setListings, setCollectionsRequiringApproval }) => ({
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setListings,
|
||||
setCollectionsRequiringApproval,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const [showWarning, setShowWarning] = useState(false)
|
||||
const [canContinue, setCanContinue] = useState(false)
|
||||
const theme = useTheme()
|
||||
const warningRef = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(warningRef, () => {
|
||||
!isNftListV2 && setShowWarning(false)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
|
||||
setListings(newListings)
|
||||
setCollectionsRequiringApproval(newCollectionsToApprove)
|
||||
setListingStatus(ListingStatus.DEFINED)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sellAssets])
|
||||
|
||||
const [
|
||||
noMarketplacesSelected,
|
||||
missingExpiration,
|
||||
invalidExpiration,
|
||||
overMaxExpiration,
|
||||
listingsMissingPrice,
|
||||
listingsBelowFloor,
|
||||
listingsAboveSellOrderFloor,
|
||||
invalidPrices,
|
||||
] = useMemo(() => {
|
||||
const noMarketplacesSelected = sellAssets.some((asset: WalletAsset) => asset.marketplaces === undefined)
|
||||
const missingExpiration = sellAssets.some((asset) => {
|
||||
return (
|
||||
asset.expirationTime != null &&
|
||||
(isNaN(asset.expirationTime) || asset.expirationTime * 1000 - Date.now() < ms`60 seconds`)
|
||||
)
|
||||
})
|
||||
const invalidExpiration = sellAssets.some((asset) => {
|
||||
return asset.expirationTime != null && isNaN(asset.expirationTime)
|
||||
})
|
||||
const overMaxExpiration = sellAssets.some((asset) => {
|
||||
return asset.expirationTime != null && asset.expirationTime - Date.now() > ms`180 days`
|
||||
})
|
||||
const listingsMissingPrice: [WalletAsset, Listing][] = []
|
||||
const listingsBelowFloor: [WalletAsset, Listing][] = []
|
||||
const listingsAboveSellOrderFloor: [WalletAsset, Listing][] = []
|
||||
const invalidPrices: [WalletAsset, Listing][] = []
|
||||
for (const asset of sellAssets) {
|
||||
if (asset.newListings) {
|
||||
for (const listing of asset.newListings) {
|
||||
if (!listing.price) listingsMissingPrice.push([asset, listing])
|
||||
else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing])
|
||||
else if (
|
||||
listing.price < (asset?.floorPrice ?? 0) * BELOW_FLOOR_PRICE_THRESHOLD &&
|
||||
!listing.overrideFloorPrice
|
||||
)
|
||||
listingsBelowFloor.push([asset, listing])
|
||||
else if (asset.floor_sell_order_price && listing.price >= asset.floor_sell_order_price)
|
||||
listingsAboveSellOrderFloor.push([asset, listing])
|
||||
}
|
||||
}
|
||||
}
|
||||
// set number of issues
|
||||
if (isNftListV2) {
|
||||
const foundIssues =
|
||||
Number(missingExpiration) +
|
||||
Number(overMaxExpiration) +
|
||||
listingsMissingPrice.length +
|
||||
listingsAboveSellOrderFloor.length
|
||||
setIssues(foundIssues)
|
||||
!foundIssues && showResolveIssues && toggleShowResolveIssues()
|
||||
// Only show Resolve Issue text if there was a user submitted error (ie not when page loads with no prices set)
|
||||
if ((missingExpiration || overMaxExpiration || listingsAboveSellOrderFloor.length) && !showResolveIssues)
|
||||
toggleShowResolveIssues()
|
||||
}
|
||||
|
||||
const continueCheck = listingsBelowFloor.length === 0 && listingsAboveSellOrderFloor.length === 0
|
||||
setCanContinue(continueCheck)
|
||||
return [
|
||||
noMarketplacesSelected,
|
||||
missingExpiration,
|
||||
invalidExpiration,
|
||||
overMaxExpiration,
|
||||
listingsMissingPrice,
|
||||
listingsBelowFloor,
|
||||
listingsAboveSellOrderFloor,
|
||||
invalidPrices,
|
||||
]
|
||||
}, [isNftListV2, sellAssets, setIssues, showResolveIssues, toggleShowResolveIssues])
|
||||
|
||||
const [disableListButton, warningMessage] = useMemo(() => {
|
||||
const disableListButton =
|
||||
noMarketplacesSelected ||
|
||||
missingExpiration ||
|
||||
invalidExpiration ||
|
||||
overMaxExpiration ||
|
||||
invalidPrices.length > 0 ||
|
||||
listingsMissingPrice.length > 0
|
||||
|
||||
const warningMessage = noMarketplacesSelected
|
||||
? 'No marketplaces selected'
|
||||
: missingExpiration
|
||||
? 'Set duration'
|
||||
: invalidExpiration
|
||||
? 'Invalid duration'
|
||||
: overMaxExpiration
|
||||
? 'Max duration is 6 months'
|
||||
: listingsMissingPrice.length > 0
|
||||
? `${listingsMissingPrice.length} item price${pluralize(listingsMissingPrice.length)} not set`
|
||||
: invalidPrices.length > 0
|
||||
? `${invalidPrices.length} price${pluralize(invalidPrices.length)} are invalid`
|
||||
: listingsBelowFloor.length > 0
|
||||
? `${listingsBelowFloor.length} item${pluralize(listingsBelowFloor.length)} listed below floor`
|
||||
: listingsAboveSellOrderFloor.length > 0
|
||||
? `${listingsAboveSellOrderFloor.length} item${pluralize(listingsAboveSellOrderFloor.length)} already listed`
|
||||
: ''
|
||||
return [disableListButton, warningMessage]
|
||||
}, [
|
||||
noMarketplacesSelected,
|
||||
missingExpiration,
|
||||
invalidExpiration,
|
||||
overMaxExpiration,
|
||||
listingsMissingPrice,
|
||||
invalidPrices,
|
||||
listingsBelowFloor,
|
||||
listingsAboveSellOrderFloor,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
setShowWarning(false)
|
||||
}, [warningMessage])
|
||||
|
||||
const addWarningMessages = () => {
|
||||
removeAllMarketplaceWarnings()
|
||||
if (!missingExpiration && !noMarketplacesSelected) {
|
||||
if (listingsMissingPrice.length > 0) {
|
||||
for (const [asset, listing] of listingsMissingPrice) {
|
||||
addMarketplaceWarning(asset, {
|
||||
message: 'PLEASE SET A PRICE',
|
||||
marketplace: listing.marketplace,
|
||||
})
|
||||
}
|
||||
} else if (invalidPrices.length > 0) {
|
||||
for (const [asset, listing] of invalidPrices) {
|
||||
!listing.overrideFloorPrice &&
|
||||
addMarketplaceWarning(asset, {
|
||||
message: `INVALID PRICE`,
|
||||
marketplace: listing.marketplace,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
setShowWarning(true)
|
||||
}
|
||||
|
||||
const warningWrappedClick = () => {
|
||||
if ((!disableListButton && canContinue) || showWarningOverride) {
|
||||
if (issues && isNftListV2) !showResolveIssues && toggleShowResolveIssues()
|
||||
else if (listingsBelowFloor.length) setShowWarning(true)
|
||||
else onClick()
|
||||
} else addWarningMessages()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative">
|
||||
{!showWarningOverride && showWarning && warningMessage.length > 0 && (
|
||||
<Row
|
||||
className={`${bodySmall} ${styles.warningTooltip}`}
|
||||
transition="250"
|
||||
onClick={() => setShowWarning(false)}
|
||||
color="textSecondary"
|
||||
zIndex="3"
|
||||
borderRadius="4"
|
||||
backgroundColor="backgroundSurface"
|
||||
height={!disableListButton ? '64' : '36'}
|
||||
maxWidth="276"
|
||||
position="absolute"
|
||||
left="24"
|
||||
bottom="52"
|
||||
flexWrap={!disableListButton ? 'wrap' : 'nowrap'}
|
||||
style={{ maxWidth: !disableListButton ? '225px' : '' }}
|
||||
ref={warningRef}
|
||||
>
|
||||
<HazardIcon />
|
||||
<Box marginLeft="4" marginRight="8">
|
||||
{warningMessage}
|
||||
</Box>
|
||||
{disableListButton ? (
|
||||
<Box paddingTop="6">
|
||||
<XMarkIcon fill={themeVars.colors.textSecondary} height="20" width="20" />
|
||||
</Box>
|
||||
) : (
|
||||
<Row
|
||||
marginLeft="72"
|
||||
cursor="pointer"
|
||||
color="accentAction"
|
||||
onClick={() => {
|
||||
setShowWarning(false)
|
||||
setCanContinue(true)
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
<ArrowRightIcon height="20" width="20" />
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
backgroundColor={showResolveIssues ? 'accentFailure' : 'accentAction'}
|
||||
cursor={
|
||||
[ListingStatus.APPROVED, ListingStatus.PENDING, ListingStatus.SIGNING].includes(listingStatus) ||
|
||||
disableListButton
|
||||
? 'default'
|
||||
: 'pointer'
|
||||
}
|
||||
className={styles.button}
|
||||
onClick={() => listingStatus !== ListingStatus.APPROVED && warningWrappedClick()}
|
||||
type="button"
|
||||
style={{
|
||||
color: showResolveIssues ? theme.accentTextDarkPrimary : theme.white,
|
||||
opacity:
|
||||
![ListingStatus.DEFINED, ListingStatus.FAILED, ListingStatus.CONTINUE].includes(listingStatus) ||
|
||||
(disableListButton && !showResolveIssues)
|
||||
? 0.3
|
||||
: 1,
|
||||
}}
|
||||
>
|
||||
{listingStatus === ListingStatus.SIGNING || listingStatus === ListingStatus.PENDING ? (
|
||||
isNftListV2 ? (
|
||||
listingStatus === ListingStatus.PENDING ? (
|
||||
'Pending'
|
||||
) : (
|
||||
'Proceed in wallet'
|
||||
)
|
||||
) : (
|
||||
<Row gap="8">
|
||||
<LoadingIcon stroke="backgroundSurface" height="20" width="20" />
|
||||
{listingStatus === ListingStatus.PENDING ? 'Pending' : 'Proceed in wallet'}
|
||||
</Row>
|
||||
)
|
||||
) : listingStatus === ListingStatus.APPROVED ? (
|
||||
'Complete!'
|
||||
) : listingStatus === ListingStatus.PAUSED ? (
|
||||
'Paused'
|
||||
) : listingStatus === ListingStatus.FAILED ? (
|
||||
'Try again'
|
||||
) : listingStatus === ListingStatus.CONTINUE ? (
|
||||
'Continue'
|
||||
) : showResolveIssues ? (
|
||||
<Plural value={issues !== 1 ? 2 : 1} _1="Resolve issue" other={t`Resolve ${issues} issues`} />
|
||||
) : (
|
||||
buttonText
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{showWarning && (
|
||||
<BelowFloorWarningModal
|
||||
listingsBelowFloor={listingsBelowFloor}
|
||||
closeModal={() => setShowWarning(false)}
|
||||
startListing={onClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { sprinkles } from 'nft/css/sprinkles.css'
|
||||
|
||||
export const chevron = style([
|
||||
sprinkles({
|
||||
height: '28',
|
||||
width: '28',
|
||||
transition: '250',
|
||||
marginLeft: 'auto',
|
||||
marginRight: '0',
|
||||
}),
|
||||
])
|
||||
|
||||
export const chevronDown = style({
|
||||
transform: 'rotate(180deg)',
|
||||
cursor: 'pointer',
|
||||
})
|
||||
|
||||
export const sectionDivider = style([
|
||||
sprinkles({
|
||||
borderRadius: '20',
|
||||
marginTop: '8',
|
||||
width: 'full',
|
||||
borderWidth: '0.5px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'backgroundOutline',
|
||||
}),
|
||||
])
|
||||
|
||||
export const button = style([
|
||||
sprinkles({
|
||||
paddingX: { sm: '12', md: '16' },
|
||||
paddingY: { sm: '10', md: '16' },
|
||||
textAlign: 'center',
|
||||
fontWeight: 'semibold',
|
||||
fontSize: { sm: '16', md: '20' },
|
||||
lineHeight: { sm: '20', md: '24' },
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'flex-end',
|
||||
borderRadius: '12',
|
||||
}),
|
||||
])
|
||||
|
||||
export const listingModalIcon = style([
|
||||
sprinkles({
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'backgroundSurface',
|
||||
}),
|
||||
{
|
||||
boxSizing: 'border-box',
|
||||
marginLeft: '-2px',
|
||||
marginRight: '4px',
|
||||
},
|
||||
])
|
||||
|
||||
export const warningTooltip = style([
|
||||
sprinkles({
|
||||
paddingTop: '8',
|
||||
paddingRight: '8',
|
||||
paddingBottom: '8',
|
||||
paddingLeft: '12',
|
||||
}),
|
||||
{
|
||||
boxShadow: '0px 4px 16px rgba(10, 10, 59, 0.2)',
|
||||
},
|
||||
])
|
||||
|
||||
export const listingSectionBorder = style([
|
||||
sprinkles({
|
||||
padding: '8',
|
||||
borderRadius: '8',
|
||||
borderColor: 'backgroundOutline',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
}),
|
||||
])
|
||||
@@ -1,322 +0,0 @@
|
||||
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { ChevronLeftIcon, XMarkIcon } from 'nft/components/icons'
|
||||
import { caption, headlineSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useBag, useIsMobile, useNFTList, useSellAsset } from 'nft/hooks'
|
||||
import { logListing, looksRareNonceFetcher } from 'nft/queries'
|
||||
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { pluralize } from 'nft/utils/roundAndPluralize'
|
||||
import { Dispatch, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
import { ListingButton } from './ListingButton'
|
||||
import * as styles from './ListingModal.css'
|
||||
import { ListingSection } from './ListingSection'
|
||||
import { approveCollectionRow, getTotalEthValue, pauseRow, resetRow, signListingRow, verifyStatus } from './utils'
|
||||
|
||||
const ListingModal = () => {
|
||||
const { provider } = useWeb3React()
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const {
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setListings,
|
||||
setCollectionsRequiringApproval,
|
||||
setListingStatusAndCallback,
|
||||
setCollectionStatusAndCallback,
|
||||
looksRareNonce,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
} = useNFTList(
|
||||
({
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setListings,
|
||||
setCollectionsRequiringApproval,
|
||||
setListingStatusAndCallback,
|
||||
setCollectionStatusAndCallback,
|
||||
looksRareNonce,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}) => ({
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setListings,
|
||||
setCollectionsRequiringApproval,
|
||||
setListingStatusAndCallback,
|
||||
setCollectionStatusAndCallback,
|
||||
looksRareNonce,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const signer = provider?.getSigner()
|
||||
const [openIndex, setOpenIndex] = useState(0)
|
||||
const [allCollectionsApproved, setAllCollectionsApproved] = useState(false)
|
||||
const toggleCart = useBag((state) => state.toggleBag)
|
||||
const looksRareNonceRef = useRef(looksRareNonce)
|
||||
const isMobile = useIsMobile()
|
||||
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
|
||||
|
||||
useEffect(() => {
|
||||
useNFTList.subscribe((state) => (looksRareNonceRef.current = state.looksRareNonce))
|
||||
}, [])
|
||||
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
|
||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrice().then((price) => {
|
||||
setEthPriceInUSD(price || 0)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const startListingEventProperties = {
|
||||
collection_addresses: sellAssets.map((asset) => asset.asset_contract.address),
|
||||
token_ids: sellAssets.map((asset) => asset.tokenId),
|
||||
marketplaces: Array.from(new Set(listings.map((asset) => asset.marketplace.name))),
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
...trace,
|
||||
}
|
||||
|
||||
// when all collections have been approved, auto start the signing process
|
||||
useEffect(() => {
|
||||
collectionsRequiringApproval?.length &&
|
||||
setAllCollectionsApproved(
|
||||
collectionsRequiringApproval.every((collection: CollectionRow) => collection.status === ListingStatus.APPROVED)
|
||||
)
|
||||
if (
|
||||
allCollectionsApproved &&
|
||||
(listingStatus === ListingStatus.PENDING ||
|
||||
listingStatus === ListingStatus.CONTINUE ||
|
||||
listingStatus === ListingStatus.SIGNING)
|
||||
) {
|
||||
resetAllRows()
|
||||
signListings()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [collectionsRequiringApproval, allCollectionsApproved])
|
||||
|
||||
const allCollectionsApprovedOrPaused = useMemo(
|
||||
() =>
|
||||
collectionsRequiringApproval.every(
|
||||
(collection: CollectionRow) =>
|
||||
collection.status === ListingStatus.APPROVED || collection.status === ListingStatus.PAUSED
|
||||
),
|
||||
[collectionsRequiringApproval]
|
||||
)
|
||||
const allListingsApprovedOrPaused = useMemo(
|
||||
() =>
|
||||
listings.every(
|
||||
(listing: ListingRow) => listing.status === ListingStatus.APPROVED || listing.status === ListingStatus.PAUSED
|
||||
),
|
||||
[listings]
|
||||
)
|
||||
|
||||
// go back to a ready state after a successful retry
|
||||
useEffect(() => {
|
||||
if (listingStatus === ListingStatus.SIGNING && allCollectionsApprovedOrPaused && allListingsApprovedOrPaused) {
|
||||
resetAllRows()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [allCollectionsApprovedOrPaused, allListingsApprovedOrPaused])
|
||||
|
||||
// handles the modal wide listing state based on conglomeration of the wallet, collection, and listing states
|
||||
const startListingFlow = async () => {
|
||||
if (!signer) return
|
||||
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
|
||||
setListingStatus(ListingStatus.SIGNING)
|
||||
const signerAddress = await signer.getAddress()
|
||||
const nonce = await looksRareNonceFetcher(signerAddress)
|
||||
setLooksRareNonce(nonce ?? 0)
|
||||
|
||||
if (!collectionsRequiringApproval?.some((collection) => collection.status === ListingStatus.PAUSED)) {
|
||||
setListingStatus(ListingStatus.SIGNING)
|
||||
setOpenIndex(1)
|
||||
}
|
||||
// for all unique collection, marketplace combos -> approve collections
|
||||
for (const collectionRow of collectionsRequiringApproval) {
|
||||
verifyStatus(collectionRow.status) &&
|
||||
(isMobile
|
||||
? await approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback, pauseAllRows)
|
||||
: approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback, pauseAllRows))
|
||||
}
|
||||
}
|
||||
|
||||
const signListings = async () => {
|
||||
if (!signer || !provider) return
|
||||
setListingStatus(ListingStatus.SIGNING)
|
||||
setOpenIndex(2)
|
||||
// sign listings
|
||||
for (const listing of listings) {
|
||||
verifyStatus(listing.status) &&
|
||||
(await signListingRow(
|
||||
listing,
|
||||
signer,
|
||||
provider,
|
||||
getLooksRareNonce,
|
||||
setLooksRareNonce,
|
||||
setListingStatusAndCallback,
|
||||
pauseAllRows
|
||||
))
|
||||
}
|
||||
const allListingsSigned = listings.every((listing: ListingRow) => listing.status === ListingStatus.APPROVED)
|
||||
const paused = listings.some((listing: ListingRow) => listing.status === ListingStatus.PAUSED)
|
||||
if (allListingsSigned) {
|
||||
setOpenIndex(0)
|
||||
setListingStatus(ListingStatus.APPROVED)
|
||||
} else if (!paused) {
|
||||
setListingStatus(ListingStatus.FAILED)
|
||||
}
|
||||
sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, {
|
||||
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
...trace,
|
||||
})
|
||||
await logListing(listings, (await signer?.getAddress()) ?? '')
|
||||
}
|
||||
|
||||
const pauseAllRows = () => {
|
||||
for (const collection of collectionsRequiringApproval) {
|
||||
pauseRow(collection, collectionsRequiringApproval, setCollectionsRequiringApproval as Dispatch<AssetRow[]>)
|
||||
}
|
||||
for (const listing of listings) {
|
||||
pauseRow(listing, listings, setListings as Dispatch<AssetRow[]>)
|
||||
}
|
||||
}
|
||||
|
||||
const resetAllRows = () => {
|
||||
for (const collection of collectionsRequiringApproval) {
|
||||
resetRow(collection, collectionsRequiringApproval, setCollectionsRequiringApproval as Dispatch<AssetRow[]>)
|
||||
}
|
||||
for (const listing of listings) {
|
||||
resetRow(listing, listings, setListings as Dispatch<AssetRow[]>)
|
||||
}
|
||||
}
|
||||
|
||||
const clickStopListing = () => {
|
||||
pauseAllRows()
|
||||
}
|
||||
|
||||
const clickStartListingFlow = () => {
|
||||
resetAllRows()
|
||||
allCollectionsApproved ? signListings() : startListingFlow()
|
||||
}
|
||||
|
||||
const showSuccessScreen = useMemo(() => listingStatus === ListingStatus.APPROVED, [listingStatus])
|
||||
|
||||
return (
|
||||
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
||||
<Column paddingTop="20" paddingBottom="20" paddingLeft="12" paddingRight="12">
|
||||
<Row className={headlineSmall} marginBottom="10">
|
||||
{isMobile && !showSuccessScreen && (
|
||||
<Box paddingTop="4" marginRight="4" onClick={toggleCart}>
|
||||
<ChevronLeftIcon height={28} width={28} />
|
||||
</Box>
|
||||
)}
|
||||
{showSuccessScreen ? 'Success!' : `Listing ${sellAssets.length} NFTs`}
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
color="textSecondary"
|
||||
backgroundColor="backgroundSurface"
|
||||
marginLeft="auto"
|
||||
marginRight="0"
|
||||
paddingRight="0"
|
||||
display={{ sm: 'flex', md: 'none' }}
|
||||
cursor="pointer"
|
||||
onClick={toggleCart}
|
||||
>
|
||||
<XMarkIcon height={28} width={28} fill={themeVars.colors.textPrimary} />
|
||||
</Box>
|
||||
</Row>
|
||||
<Column overflowX="hidden" overflowY="auto" style={{ maxHeight: '60vh' }}>
|
||||
{showSuccessScreen ? (
|
||||
<Trace
|
||||
name={NFTEventName.NFT_LISTING_COMPLETED}
|
||||
properties={{ list_quantity: listings.length, usd_value: ethPriceInUSD * totalEthListingValue, ...trace }}
|
||||
shouldLogImpression
|
||||
>
|
||||
<ListingSection
|
||||
sectionTitle={`Listed ${listings.length} item${pluralize(listings.length)} for sale`}
|
||||
rows={listings}
|
||||
index={0}
|
||||
openIndex={openIndex}
|
||||
isSuccessScreen={true}
|
||||
/>
|
||||
</Trace>
|
||||
) : (
|
||||
<>
|
||||
<ListingSection
|
||||
sectionTitle={`Approve ${collectionsRequiringApproval.length} collection${pluralize(
|
||||
collectionsRequiringApproval.length
|
||||
)}`}
|
||||
title="COLLECTIONS"
|
||||
rows={collectionsRequiringApproval}
|
||||
index={1}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
<ListingSection
|
||||
sectionTitle={`Confirm ${listings.length} listing${pluralize(listings.length)}`}
|
||||
caption="Now you can sign to list each item"
|
||||
title="NFTS"
|
||||
rows={listings}
|
||||
index={2}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
<hr className={styles.sectionDivider} />
|
||||
<Row className={subhead} marginTop="12" marginBottom={showSuccessScreen ? '8' : '20'}>
|
||||
Return if sold
|
||||
<Row className={subheadSmall} marginLeft="auto" marginRight="0">
|
||||
{totalEthListingValue}
|
||||
ETH
|
||||
</Row>
|
||||
</Row>
|
||||
{showSuccessScreen ? (
|
||||
<Box as="span" className={caption} color="textSecondary">
|
||||
Status:{' '}
|
||||
<Box as="span" color="accentSuccess">
|
||||
Confirmed
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<ListingButton onClick={clickStartListingFlow} buttonText="Start listing" showWarningOverride={isMobile} />
|
||||
)}
|
||||
{(listingStatus === ListingStatus.PENDING || listingStatus === ListingStatus.SIGNING) && (
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
backgroundColor="backgroundSurface"
|
||||
cursor="pointer"
|
||||
color="orange"
|
||||
className={styles.button}
|
||||
onClick={clickStopListing}
|
||||
type="button"
|
||||
>
|
||||
Stop listing
|
||||
</Box>
|
||||
)}
|
||||
</Column>
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListingModal
|
||||
@@ -1,187 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { ApprovedCheckmarkIcon, ChevronUpIcon, FailedListingIcon, LoadingIcon } from 'nft/components/icons'
|
||||
import { badge, bodySmall, buttonTextSmall, subhead } from 'nft/css/common.css'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
|
||||
import { formatEthPrice, numberToWei } from 'nft/utils/currency'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import * as styles from './ListingModal.css'
|
||||
|
||||
export const ListingSection = ({
|
||||
sectionTitle,
|
||||
caption = undefined,
|
||||
title = undefined,
|
||||
rows,
|
||||
index,
|
||||
openIndex,
|
||||
isSuccessScreen = false,
|
||||
}: {
|
||||
sectionTitle: string
|
||||
caption?: string
|
||||
title?: string
|
||||
rows: AssetRow[]
|
||||
index: number
|
||||
openIndex: number
|
||||
isSuccessScreen?: boolean
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const notAllApproved = rows.some((row: AssetRow) => row.status !== ListingStatus.APPROVED)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
|
||||
|
||||
const removeRow = (row: any) => {
|
||||
// collections
|
||||
if (index === 1) {
|
||||
for (const asset of sellAssets)
|
||||
if (asset.asset_contract.address === row.collectionAddress) removeAssetMarketplace(asset, row.marketplace)
|
||||
}
|
||||
// listings
|
||||
else removeAssetMarketplace(row.asset, row.marketplace)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(index === openIndex)
|
||||
}, [index, openIndex])
|
||||
|
||||
function getListingRowPrice(row: AssetRow): number | undefined {
|
||||
const listingRow = row as ListingRow
|
||||
const newListings = listingRow.asset.newListings
|
||||
return newListings?.find((listing) => listing.marketplace.name === listingRow.marketplace.name)?.price ?? 0
|
||||
}
|
||||
|
||||
const allApproved = !notAllApproved && rows.length > 0 && !isSuccessScreen
|
||||
|
||||
return (
|
||||
<Row
|
||||
flexWrap="wrap"
|
||||
className={subhead}
|
||||
marginTop="10"
|
||||
marginBottom="10"
|
||||
onClick={() => rows.length > 0 && setIsOpen(!isOpen)}
|
||||
color={allApproved ? 'accentSuccess' : 'textPrimary'}
|
||||
>
|
||||
{allApproved && <ApprovedCheckmarkIcon style={{ marginRight: '8px' }} />}
|
||||
{sectionTitle}
|
||||
{!isSuccessScreen && <ChevronUpIcon className={clsx(`${isOpen ? '' : styles.chevronDown} ${styles.chevron}`)} />}
|
||||
{(isOpen || isSuccessScreen) && (
|
||||
<Column
|
||||
gap="12"
|
||||
width="full"
|
||||
paddingTop={isSuccessScreen ? '28' : 'auto'}
|
||||
className={clsx(!isSuccessScreen && styles.listingSectionBorder)}
|
||||
>
|
||||
{caption && (
|
||||
<Box color="textPrimary" fontWeight="normal" className={caption}>
|
||||
{caption}
|
||||
</Box>
|
||||
)}
|
||||
{title && (
|
||||
<Box color="textSecondary" className={badge}>
|
||||
{title}
|
||||
</Box>
|
||||
)}
|
||||
<Column gap="8">
|
||||
{rows.map((row: AssetRow, index) => {
|
||||
return (
|
||||
<Column key={index} gap="8">
|
||||
<Row>
|
||||
{row.images?.map((image, index) => {
|
||||
return (
|
||||
<Box
|
||||
as="img"
|
||||
height="20"
|
||||
width="20"
|
||||
borderRadius={index === 0 && (row as CollectionRow).collectionAddress ? 'round' : '4'}
|
||||
style={{ zIndex: 2 - index }}
|
||||
className={styles.listingModalIcon}
|
||||
src={image}
|
||||
alt={row.name}
|
||||
key={index}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<Box
|
||||
marginLeft="8"
|
||||
marginRight="auto"
|
||||
fontWeight="normal"
|
||||
color="textPrimary"
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
maxWidth={{
|
||||
sm: 'max',
|
||||
md:
|
||||
row.status === ListingStatus.REJECTED || row.status === ListingStatus.FAILED ? '120' : 'full',
|
||||
}}
|
||||
className={bodySmall}
|
||||
>
|
||||
{row.name}
|
||||
</Box>
|
||||
{isSuccessScreen ? (
|
||||
getListingRowPrice(row) &&
|
||||
`${formatEthPrice(numberToWei(getListingRowPrice(row) ?? 0).toString())} ETH`
|
||||
) : row.status === ListingStatus.APPROVED ? (
|
||||
<ApprovedCheckmarkIcon height="20" width="20" />
|
||||
) : row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED ? (
|
||||
<Row gap="4">
|
||||
<Box fontWeight="normal" fontSize="14" color="textSecondary">
|
||||
{row.status}
|
||||
</Box>
|
||||
<FailedListingIcon />
|
||||
</Row>
|
||||
) : (
|
||||
row.status === ListingStatus.SIGNING && <LoadingIcon height="20" width="20" stroke="#4673FA" />
|
||||
)}
|
||||
</Row>
|
||||
{(row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED) && (
|
||||
<Row gap="8" justifyContent="center">
|
||||
<Box
|
||||
width="120"
|
||||
as="button"
|
||||
className={buttonTextSmall}
|
||||
borderRadius="12"
|
||||
border="none"
|
||||
color="red400"
|
||||
height="32"
|
||||
cursor="pointer"
|
||||
style={{ backgroundColor: '#FA2B391A' }}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation()
|
||||
removeRow(row)
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Box>
|
||||
<Box
|
||||
width="120"
|
||||
as="button"
|
||||
className={buttonTextSmall}
|
||||
borderRadius="12"
|
||||
border="none"
|
||||
color="accentAction"
|
||||
height="32"
|
||||
cursor="pointer"
|
||||
style={{ backgroundColor: '#4C82FB29' }}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation()
|
||||
if (row.callback) {
|
||||
await row.callback()
|
||||
}
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</Box>
|
||||
</Row>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
})}
|
||||
</Column>
|
||||
</Column>
|
||||
)}
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
|
||||
import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries'
|
||||
import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
|
||||
import { AssetRow, CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
|
||||
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
|
||||
import { Dispatch } from 'react'
|
||||
|
||||
const updateStatus = ({
|
||||
listing,
|
||||
newStatus,
|
||||
rows,
|
||||
setRows,
|
||||
callback,
|
||||
}: {
|
||||
listing: AssetRow
|
||||
newStatus: ListingStatus
|
||||
rows: AssetRow[]
|
||||
setRows: Dispatch<AssetRow[]>
|
||||
callback?: () => Promise<void>
|
||||
}) => {
|
||||
const rowsCopy = [...rows]
|
||||
const index = rows.findIndex((n) => n === listing)
|
||||
listing.status = newStatus
|
||||
if (callback) listing.callback = callback
|
||||
rowsCopy[index] = listing
|
||||
setRows(rowsCopy)
|
||||
}
|
||||
|
||||
export async function approveCollectionRow(
|
||||
collectionRow: CollectionRow,
|
||||
signer: JsonRpcSigner,
|
||||
setCollectionStatusAndCallback: (
|
||||
collection: CollectionRow,
|
||||
status: ListingStatus,
|
||||
callback?: () => Promise<void>
|
||||
) => void,
|
||||
pauseAllRows?: () => void
|
||||
) {
|
||||
const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback, pauseAllRows)
|
||||
setCollectionStatusAndCallback(collectionRow, ListingStatus.SIGNING, callback)
|
||||
const { marketplace, collectionAddress } = collectionRow
|
||||
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
|
||||
const spender =
|
||||
marketplace.name === 'OpenSea'
|
||||
? OPENSEA_CROSS_CHAIN_CONDUIT
|
||||
: marketplace.name === 'Rarible'
|
||||
? LOOKSRARE_MARKETPLACE_CONTRACT
|
||||
: marketplace.name === 'X2Y2'
|
||||
? X2Y2_TRANSFER_CONTRACT
|
||||
: addresses.TRANSFER_MANAGER_ERC721
|
||||
!!collectionAddress &&
|
||||
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
|
||||
setCollectionStatusAndCallback(collectionRow, newStatus, callback)
|
||||
))
|
||||
if (
|
||||
(collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) &&
|
||||
pauseAllRows
|
||||
)
|
||||
pauseAllRows()
|
||||
}
|
||||
|
||||
export async function signListingRow(
|
||||
listing: ListingRow,
|
||||
signer: JsonRpcSigner,
|
||||
provider: Web3Provider,
|
||||
getLooksRareNonce: () => number,
|
||||
setLooksRareNonce: (nonce: number) => void,
|
||||
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void,
|
||||
pauseAllRows?: () => void
|
||||
) {
|
||||
const looksRareNonce = getLooksRareNonce()
|
||||
const callback = () => {
|
||||
return signListingRow(
|
||||
listing,
|
||||
signer,
|
||||
provider,
|
||||
getLooksRareNonce,
|
||||
setLooksRareNonce,
|
||||
setListingStatusAndCallback,
|
||||
pauseAllRows
|
||||
)
|
||||
}
|
||||
setListingStatusAndCallback(listing, ListingStatus.SIGNING, callback)
|
||||
const { asset, marketplace } = listing
|
||||
const res = await signListing(marketplace, asset, signer, provider, looksRareNonce, (newStatus: ListingStatus) =>
|
||||
setListingStatusAndCallback(listing, newStatus, callback)
|
||||
)
|
||||
if (listing.status === ListingStatus.REJECTED && pauseAllRows) {
|
||||
pauseAllRows()
|
||||
} else {
|
||||
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
|
||||
}
|
||||
}
|
||||
|
||||
export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
|
||||
const total = sellAssets.reduce((total, asset: WalletAsset) => {
|
||||
if (asset.newListings?.length) {
|
||||
const maxListing = asset.newListings.reduce((a, b) => ((a.price ?? 0) > (b.price ?? 0) ? a : b))
|
||||
// LooksRare is a unique case where creator royalties are a flat 0.5% or 50 basis points
|
||||
const maxFee =
|
||||
maxListing.marketplace.fee +
|
||||
(maxListing.marketplace.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset?.basisPoints ?? 0) / 100
|
||||
return total + (maxListing.price ?? 0) - (maxListing.price ?? 0) * (maxFee / 100)
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
return total ? Math.round(total * 10000 + Number.EPSILON) / 10000 : 0
|
||||
}
|
||||
|
||||
export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => {
|
||||
const newCollectionsToApprove: CollectionRow[] = []
|
||||
|
||||
const newListings: ListingRow[] = []
|
||||
sellAssets.forEach((asset) => {
|
||||
asset.marketplaces?.forEach((marketplace: ListingMarket) => {
|
||||
const newListing = {
|
||||
images: [asset.smallImageUrl, marketplace.icon],
|
||||
name: asset.name || `#${asset.tokenId}`,
|
||||
status: ListingStatus.DEFINED,
|
||||
asset,
|
||||
marketplace,
|
||||
price: asset.newListings?.find((listing) => listing.marketplace.name === marketplace.name)?.price,
|
||||
}
|
||||
newListings.push(newListing)
|
||||
if (
|
||||
!newCollectionsToApprove.some(
|
||||
(collectionRow: CollectionRow) =>
|
||||
collectionRow.collectionAddress === asset.asset_contract.address &&
|
||||
collectionRow.marketplace.name === marketplace.name
|
||||
)
|
||||
) {
|
||||
const newCollectionRow = {
|
||||
images: [asset.asset_contract.image_url, marketplace.icon],
|
||||
name: asset.asset_contract.name,
|
||||
status: ListingStatus.DEFINED,
|
||||
collectionAddress: asset.asset_contract.address,
|
||||
isVerified: asset.collectionIsVerified,
|
||||
marketplace,
|
||||
}
|
||||
newCollectionsToApprove.push(newCollectionRow)
|
||||
}
|
||||
})
|
||||
})
|
||||
return [newCollectionsToApprove, newListings]
|
||||
}
|
||||
|
||||
type ListingState = {
|
||||
allListingsPending: boolean
|
||||
allListingsDefined: boolean
|
||||
allListingsApproved: boolean
|
||||
allCollectionsPending: boolean
|
||||
allCollectionsDefined: boolean
|
||||
anyActiveSigning: boolean
|
||||
anyActiveFailures: boolean
|
||||
anyActiveRejections: boolean
|
||||
anyPaused: boolean
|
||||
}
|
||||
|
||||
export const getListingState = (
|
||||
collectionsRequiringApproval: CollectionRow[],
|
||||
listings: ListingRow[]
|
||||
): ListingState => {
|
||||
let allListingsPending = true
|
||||
let allListingsDefined = true
|
||||
let allListingsApproved = true
|
||||
let allCollectionsPending = true
|
||||
let allCollectionsDefined = true
|
||||
let anyActiveSigning = false
|
||||
let anyActiveFailures = false
|
||||
let anyActiveRejections = false
|
||||
let anyPaused = false
|
||||
|
||||
if (collectionsRequiringApproval.length === 0) {
|
||||
allCollectionsDefined = allCollectionsPending = false
|
||||
}
|
||||
for (const collection of collectionsRequiringApproval) {
|
||||
if (collection.status !== ListingStatus.PENDING) allCollectionsPending = false
|
||||
if (collection.status !== ListingStatus.DEFINED) allCollectionsDefined = false
|
||||
if (collection.status === ListingStatus.SIGNING) anyActiveSigning = true
|
||||
else if (collection.status === ListingStatus.FAILED) anyActiveFailures = true
|
||||
else if (collection.status === ListingStatus.REJECTED) anyActiveRejections = true
|
||||
else if (collection.status === ListingStatus.PAUSED) anyPaused = true
|
||||
}
|
||||
|
||||
if (listings.length === 0) {
|
||||
allListingsApproved = allListingsDefined = allListingsPending = false
|
||||
}
|
||||
for (const listing of listings) {
|
||||
if (listing.status !== ListingStatus.PENDING) allListingsPending = false
|
||||
if (listing.status !== ListingStatus.DEFINED) allListingsDefined = false
|
||||
if (listing.status !== ListingStatus.APPROVED) allListingsApproved = false
|
||||
if (listing.status === ListingStatus.SIGNING) anyActiveSigning = true
|
||||
else if (listing.status === ListingStatus.FAILED) anyActiveFailures = true
|
||||
else if (listing.status === ListingStatus.REJECTED) anyActiveRejections = true
|
||||
else if (listing.status === ListingStatus.PAUSED) anyPaused = true
|
||||
}
|
||||
return {
|
||||
allListingsPending,
|
||||
allListingsDefined,
|
||||
allListingsApproved,
|
||||
allCollectionsPending,
|
||||
allCollectionsDefined,
|
||||
anyActiveSigning,
|
||||
anyActiveFailures,
|
||||
anyActiveRejections,
|
||||
anyPaused,
|
||||
}
|
||||
}
|
||||
|
||||
export const verifyStatus = (status: ListingStatus) => {
|
||||
return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED
|
||||
}
|
||||
|
||||
export const pauseRow = (row: AssetRow, rows: AssetRow[], setRows: Dispatch<AssetRow[]>) => {
|
||||
if (row.status === ListingStatus.PENDING || row.status === ListingStatus.DEFINED)
|
||||
updateStatus({
|
||||
listing: row,
|
||||
newStatus: ListingStatus.PAUSED,
|
||||
rows,
|
||||
setRows,
|
||||
})
|
||||
}
|
||||
|
||||
export const resetRow = (row: AssetRow, rows: AssetRow[], setRows: Dispatch<AssetRow[]>) => {
|
||||
if (
|
||||
row.status === ListingStatus.PAUSED ||
|
||||
row.status === ListingStatus.FAILED ||
|
||||
row.status === ListingStatus.REJECTED
|
||||
)
|
||||
updateStatus({
|
||||
listing: row,
|
||||
newStatus: ListingStatus.DEFINED,
|
||||
rows,
|
||||
setRows,
|
||||
})
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { calc } from '@vanilla-extract/css-utils'
|
||||
import { sprinkles, themeVars, vars } from 'nft/css/sprinkles.css'
|
||||
|
||||
export const card = style([
|
||||
sprinkles({
|
||||
overflow: 'hidden',
|
||||
paddingBottom: '12',
|
||||
borderRadius: '16',
|
||||
}),
|
||||
{
|
||||
boxSizing: 'border-box',
|
||||
WebkitBoxSizing: 'border-box',
|
||||
boxShadow: vars.color.cardDropShadow,
|
||||
backgroundColor: themeVars.colors.backgroundSurface,
|
||||
':after': {
|
||||
content: '',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: ' 0px',
|
||||
bottom: ' 0px',
|
||||
left: '0px',
|
||||
border: ' 1px solid',
|
||||
borderRadius: '16px',
|
||||
borderColor: '#5D678524',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const loadingBackground = style({
|
||||
background: `linear-gradient(270deg, ${themeVars.colors.backgroundOutline} 0%, ${themeVars.colors.backgroundSurface} 100%)`,
|
||||
})
|
||||
|
||||
export const cardImageHover = style({
|
||||
transform: 'scale(1.15)',
|
||||
})
|
||||
|
||||
export const selectedCard = style([
|
||||
card,
|
||||
sprinkles({
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
{
|
||||
':after': {
|
||||
border: '2px solid',
|
||||
borderColor: vars.color.accentAction,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const button = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
width: 'full',
|
||||
position: 'relative',
|
||||
paddingY: '8',
|
||||
marginTop: { sm: '8', md: '10' },
|
||||
marginBottom: '12',
|
||||
borderRadius: '12',
|
||||
border: 'none',
|
||||
justifyContent: 'center',
|
||||
transition: '250',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{
|
||||
lineHeight: '16px',
|
||||
},
|
||||
])
|
||||
|
||||
export const marketplaceIcon = style([
|
||||
sprinkles({
|
||||
display: 'inline-block',
|
||||
width: '16',
|
||||
height: '16',
|
||||
borderRadius: '4',
|
||||
flexShrink: '0',
|
||||
marginLeft: '8',
|
||||
}),
|
||||
{
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
])
|
||||
|
||||
export const rarityInfo = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
borderRadius: '4',
|
||||
height: '16',
|
||||
color: 'textPrimary',
|
||||
background: 'backgroundInteractive',
|
||||
fontSize: '10',
|
||||
fontWeight: 'semibold',
|
||||
paddingX: '4',
|
||||
}),
|
||||
{
|
||||
lineHeight: '12px',
|
||||
letterSpacing: '0.04em',
|
||||
backdropFilter: 'blur(6px)',
|
||||
},
|
||||
])
|
||||
|
||||
export const playbackSwitch = style([
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
width: '40',
|
||||
height: '40',
|
||||
zIndex: '1',
|
||||
}),
|
||||
{
|
||||
marginLeft: calc.subtract('100%', '50px'),
|
||||
transform: 'translateY(-56px)',
|
||||
},
|
||||
])
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import clsx from 'clsx'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import {
|
||||
MinusIconLarge,
|
||||
PauseButtonIcon,
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
RarityVerifiedIcon,
|
||||
VerifiedIcon,
|
||||
} from 'nft/components/icons'
|
||||
import { body, bodySmall, buttonTextMedium, subhead } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { GenieAsset, Rarity, UniformAspectRatio, UniformAspectRatios, WalletAsset } from 'nft/types'
|
||||
import { fallbackProvider, isAudio, isVideo, putCommas } from 'nft/utils'
|
||||
@@ -32,11 +30,11 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { AlertTriangle, Pause, Play } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import * as styles from './Card.css'
|
||||
import { colors } from 'theme/colors'
|
||||
import { opacify } from 'theme/utils'
|
||||
|
||||
/* -------- ASSET CONTEXT -------- */
|
||||
export interface CardContextProps {
|
||||
@@ -166,6 +164,31 @@ const StyledImageContainer = styled.div<{ isDisabled?: boolean }>`
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
|
||||
`
|
||||
|
||||
const CardContainer = styled.div<{ selected: boolean }>`
|
||||
position: relative;
|
||||
border-radius: ${BORDER_RADIUS}px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
overflow: hidden;
|
||||
padding-bottom: 12px;
|
||||
border-radius: 16px;
|
||||
box-shadow: rgba(0, 0, 0, 10%) 0px 4px 12px;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
|
||||
:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
border: ${({ selected }) => (selected ? '2px' : '1px')} solid;
|
||||
border-radius: 16px;
|
||||
border-color: ${({ theme, selected }) => (selected ? theme.accentAction : opacify(12, colors.gray500))};
|
||||
pointer-events: none;
|
||||
}
|
||||
`
|
||||
|
||||
/* -------- ASSET CARD -------- */
|
||||
interface CardProps {
|
||||
asset: GenieAsset | WalletAsset
|
||||
@@ -220,19 +243,16 @@ const Container = ({
|
||||
|
||||
return (
|
||||
<CardContext.Provider value={providerValue}>
|
||||
<Box
|
||||
position="relative"
|
||||
<CardContainer
|
||||
selected={selected}
|
||||
ref={assetRef}
|
||||
borderRadius={BORDER_RADIUS}
|
||||
className={selected ? styles.selectedCard : styles.card}
|
||||
draggable={false}
|
||||
onMouseEnter={toggleHover}
|
||||
onMouseLeave={toggleHover}
|
||||
transition="250"
|
||||
onClick={isDisabled ? () => null : onClick ?? handleAssetInBag}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</CardContainer>
|
||||
</CardContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -277,6 +297,13 @@ function getHeightFromAspectRatio(uniformAspectRatio: UniformAspectRatio, render
|
||||
: renderedHeight
|
||||
}
|
||||
|
||||
function getMediaAspectRatio(
|
||||
uniformAspectRatio?: UniformAspectRatio,
|
||||
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
|
||||
): string {
|
||||
return uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
|
||||
}
|
||||
|
||||
interface ImageProps {
|
||||
uniformAspectRatio?: UniformAspectRatio
|
||||
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
|
||||
@@ -284,6 +311,29 @@ interface ImageProps {
|
||||
setRenderedHeight?: (renderedHeight: number | undefined) => void
|
||||
}
|
||||
|
||||
const StyledMediaContainer = styled(Row)`
|
||||
overflow: hidden;
|
||||
border-top-left-radius: ${BORDER_RADIUS}px;
|
||||
border-top-right-radius: ${BORDER_RADIUS}px;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img<{
|
||||
hovered: boolean
|
||||
imageLoading: boolean
|
||||
$aspectRatio?: string
|
||||
$hidden?: boolean
|
||||
}>`
|
||||
width: 100%;
|
||||
aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};
|
||||
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} transform`};
|
||||
will-change: transform;
|
||||
object-fit: contain;
|
||||
visibility: ${({ $hidden }) => ($hidden ? 'hidden' : 'visible')};
|
||||
transform: ${({ hovered }) => hovered && 'scale(1.15)'};
|
||||
background: ${({ theme, imageLoading }) =>
|
||||
imageLoading && `linear-gradient(270deg, ${theme.backgroundOutline} 0%, ${theme.backgroundSurface} 100%)`};
|
||||
`
|
||||
|
||||
const Image = ({
|
||||
uniformAspectRatio = UniformAspectRatios.square,
|
||||
setUniformAspectRatio,
|
||||
@@ -300,40 +350,50 @@ const Image = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display="flex" overflow="hidden" borderTopLeftRadius={BORDER_RADIUS} borderTopRightRadius={BORDER_RADIUS}>
|
||||
<Box
|
||||
as="img"
|
||||
width="full"
|
||||
style={{
|
||||
aspectRatio: `${uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'}`,
|
||||
transition: 'transform 0.25s ease 0s',
|
||||
}}
|
||||
<StyledMediaContainer>
|
||||
<StyledImage
|
||||
src={asset.imageUrl || asset.smallImageUrl}
|
||||
objectFit="contain"
|
||||
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
|
||||
hovered={hovered && !isMobile}
|
||||
imageLoading={!loaded}
|
||||
draggable={false}
|
||||
onError={() => setNoContent(true)}
|
||||
onLoad={(e) => {
|
||||
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
|
||||
setLoaded(true)
|
||||
}}
|
||||
className={clsx(hovered && !isMobile && styles.cardImageHover, !loaded && styles.loadingBackground)}
|
||||
/>
|
||||
</Box>
|
||||
</StyledMediaContainer>
|
||||
)
|
||||
}
|
||||
|
||||
function getMediaAspectRatio(
|
||||
uniformAspectRatio: UniformAspectRatio,
|
||||
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
|
||||
): string {
|
||||
return uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
|
||||
}
|
||||
|
||||
interface MediaProps {
|
||||
shouldPlay: boolean
|
||||
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
|
||||
}
|
||||
|
||||
const PlaybackButton = styled.div`
|
||||
position: absolute;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
z-index: 1;
|
||||
margin-left: calc(100% - 50px);
|
||||
transform: translateY(-56px);
|
||||
`
|
||||
|
||||
const StyledVideo = styled.video<{
|
||||
$aspectRatio?: string
|
||||
}>`
|
||||
width: 100%;
|
||||
aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};
|
||||
`
|
||||
|
||||
const StyledInnerMediaContainer = styled(Row)`
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
`
|
||||
|
||||
const Video = ({
|
||||
uniformAspectRatio = UniformAspectRatios.square,
|
||||
setUniformAspectRatio,
|
||||
@@ -360,52 +420,38 @@ const Video = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" overflow="hidden">
|
||||
<Box
|
||||
as="img"
|
||||
alt={asset.name || asset.tokenId}
|
||||
width="full"
|
||||
style={{
|
||||
aspectRatio: getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio),
|
||||
transition: 'transform 0.25s ease 0s',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
<StyledMediaContainer>
|
||||
<StyledImage
|
||||
src={asset.imageUrl || asset.smallImageUrl}
|
||||
objectFit="contain"
|
||||
alt={asset.name || asset.tokenId}
|
||||
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
|
||||
hovered={hovered && !isMobile}
|
||||
imageLoading={!imageLoaded}
|
||||
draggable={false}
|
||||
onError={() => setNoContent(true)}
|
||||
onLoad={(e) => {
|
||||
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
|
||||
setImageLoaded(true)
|
||||
}}
|
||||
visibility={shouldPlay ? 'hidden' : 'visible'}
|
||||
className={clsx(hovered && !isMobile && styles.cardImageHover, !imageLoaded && styles.loadingBackground)}
|
||||
$hidden={shouldPlay}
|
||||
/>
|
||||
</Box>
|
||||
</StyledMediaContainer>
|
||||
{shouldPlay ? (
|
||||
<>
|
||||
<Box className={styles.playbackSwitch}>
|
||||
<PauseButtonIcon
|
||||
width="100%"
|
||||
height="100%"
|
||||
<PlaybackButton>
|
||||
<Pause
|
||||
size="24px"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setCurrentTokenPlayingMedia(undefined)
|
||||
}}
|
||||
className="playback-icon"
|
||||
/>
|
||||
</Box>
|
||||
<Box position="absolute" left="0" top="0" display="flex">
|
||||
<Box
|
||||
as="video"
|
||||
</PlaybackButton>
|
||||
<StyledInnerMediaContainer>
|
||||
<StyledVideo
|
||||
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
|
||||
ref={vidRef}
|
||||
width="full"
|
||||
style={{
|
||||
aspectRatio: `${
|
||||
uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
|
||||
}`,
|
||||
}}
|
||||
onEnded={(e) => {
|
||||
e.preventDefault()
|
||||
setCurrentTokenPlayingMedia(undefined)
|
||||
@@ -414,29 +460,32 @@ const Video = ({
|
||||
playsInline
|
||||
>
|
||||
<source src={asset.animationUrl} />
|
||||
</Box>
|
||||
</Box>
|
||||
</StyledVideo>
|
||||
</StyledInnerMediaContainer>
|
||||
</>
|
||||
) : (
|
||||
<Box className={styles.playbackSwitch}>
|
||||
<PlaybackButton>
|
||||
{((!isMobile && hovered) || isMobile) && (
|
||||
<PlayButtonIcon
|
||||
width="100%"
|
||||
height="100%"
|
||||
<Play
|
||||
size="24px"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setCurrentTokenPlayingMedia(asset.tokenId)
|
||||
}}
|
||||
className="playback-icon"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</PlaybackButton>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledAudio = styled.audio`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const Audio = ({
|
||||
uniformAspectRatio = UniformAspectRatios.square,
|
||||
setUniformAspectRatio,
|
||||
@@ -463,29 +512,25 @@ const Audio = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" overflow="hidden">
|
||||
<Box
|
||||
as="img"
|
||||
alt={asset.name || asset.tokenId}
|
||||
width="full"
|
||||
style={{
|
||||
aspectRatio: getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio),
|
||||
transition: 'transform 0.4s ease 0s',
|
||||
}}
|
||||
<StyledMediaContainer>
|
||||
<StyledImage
|
||||
src={asset.imageUrl || asset.smallImageUrl}
|
||||
objectFit="contain"
|
||||
alt={asset.name || asset.tokenId}
|
||||
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
|
||||
hovered={hovered && !isMobile}
|
||||
imageLoading={!imageLoaded}
|
||||
draggable={false}
|
||||
onError={() => setNoContent(true)}
|
||||
onLoad={(e) => {
|
||||
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
|
||||
setImageLoaded(true)
|
||||
setImageLoaded(true)
|
||||
}}
|
||||
className={clsx(hovered && !isMobile && styles.cardImageHover, !imageLoaded && styles.loadingBackground)}
|
||||
/>
|
||||
</Box>
|
||||
</StyledMediaContainer>
|
||||
{shouldPlay ? (
|
||||
<>
|
||||
<Box className={styles.playbackSwitch}>
|
||||
<PlaybackButton>
|
||||
<PauseButtonIcon
|
||||
width="100%"
|
||||
height="100%"
|
||||
@@ -494,26 +539,22 @@ const Audio = ({
|
||||
e.stopPropagation()
|
||||
setCurrentTokenPlayingMedia(undefined)
|
||||
}}
|
||||
className="playback-icon"
|
||||
/>
|
||||
</Box>
|
||||
<Box position="absolute" left="0" top="0" display="flex">
|
||||
<Box
|
||||
as="audio"
|
||||
</PlaybackButton>
|
||||
<StyledInnerMediaContainer>
|
||||
<StyledAudio
|
||||
ref={audRef}
|
||||
width="full"
|
||||
height="full"
|
||||
onEnded={(e) => {
|
||||
e.preventDefault()
|
||||
setCurrentTokenPlayingMedia(undefined)
|
||||
}}
|
||||
>
|
||||
<source src={asset.animationUrl} />
|
||||
</Box>
|
||||
</Box>
|
||||
</StyledAudio>
|
||||
</StyledInnerMediaContainer>
|
||||
</>
|
||||
) : (
|
||||
<Box className={styles.playbackSwitch}>
|
||||
<PlaybackButton>
|
||||
{((!isMobile && hovered) || isMobile) && (
|
||||
<PlayButtonIcon
|
||||
width="100%"
|
||||
@@ -523,10 +564,9 @@ const Audio = ({
|
||||
e.stopPropagation()
|
||||
setCurrentTokenPlayingMedia(asset.tokenId)
|
||||
}}
|
||||
className="playback-icon"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</PlaybackButton>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
@@ -537,36 +577,39 @@ interface CardDetailsContainerProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const StyledDetailsContainer = styled(Column)`
|
||||
position: relative;
|
||||
padding: 12px 12px 0px;
|
||||
justify-content: space-between;
|
||||
transition: ${({ theme }) => `${theme.transition.duration.medium}`};
|
||||
`
|
||||
|
||||
const DetailsContainer = ({ children }: CardDetailsContainerProps) => {
|
||||
return (
|
||||
<Row
|
||||
position="relative"
|
||||
paddingX="12"
|
||||
paddingTop="12"
|
||||
justifyContent="space-between"
|
||||
flexDirection="column"
|
||||
transition="250"
|
||||
>
|
||||
{children}
|
||||
</Row>
|
||||
)
|
||||
return <StyledDetailsContainer>{children}</StyledDetailsContainer>
|
||||
}
|
||||
|
||||
const StyledInfoContainer = styled.div`
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const InfoContainer = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<Box overflow="hidden" width="full">
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
return <StyledInfoContainer>{children}</StyledInfoContainer>
|
||||
}
|
||||
|
||||
const TruncatedTextRow = styled(Row)`
|
||||
const TruncatedTextRow = styled(ThemedText.BodySmall)`
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
white-space: pre;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
const AssetNameRow = styled(TruncatedTextRow)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 16px !important;
|
||||
font-weight: 400;
|
||||
`
|
||||
|
||||
interface ProfileNftDetailsProps {
|
||||
@@ -574,6 +617,18 @@ interface ProfileNftDetailsProps {
|
||||
hideDetails: boolean
|
||||
}
|
||||
|
||||
const PrimaryRowContainer = styled.div`
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const FloorPriceRow = styled(TruncatedTextRow)`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
|
||||
const assetName = () => {
|
||||
if (!asset.name && !asset.tokenId) return
|
||||
@@ -583,89 +638,95 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
|
||||
const shouldShowUserListedPrice = !asset.notForSale && asset.asset_contract.tokenType !== NftStandard.Erc1155
|
||||
|
||||
return (
|
||||
<Box overflow="hidden" width="full" flexWrap="nowrap">
|
||||
<PrimaryRowContainer>
|
||||
<PrimaryRow>
|
||||
<PrimaryDetails>
|
||||
<TruncatedTextRow className={bodySmall} style={{ color: themeVars.colors.textSecondary }}>
|
||||
<TruncatedTextRow color="textSecondary">
|
||||
{!!asset.asset_contract.name && <span>{asset.asset_contract.name}</span>}
|
||||
</TruncatedTextRow>
|
||||
{asset.collectionIsVerified && <VerifiedIcon height="18px" width="18px" />}
|
||||
</PrimaryDetails>
|
||||
{!hideDetails && <DetailsLink />}
|
||||
</PrimaryRow>
|
||||
<Row justifyItems="flex-start">
|
||||
<TruncatedTextRow
|
||||
className={body}
|
||||
style={{
|
||||
color: themeVars.colors.textPrimary,
|
||||
}}
|
||||
>
|
||||
{assetName()}
|
||||
</TruncatedTextRow>
|
||||
<Row>
|
||||
<AssetNameRow>{assetName()}</AssetNameRow>
|
||||
{asset.susFlag && <Suspicious />}
|
||||
</Row>
|
||||
<TruncatedTextRow className={buttonTextMedium} style={{ color: themeVars.colors.textPrimary }}>
|
||||
<FloorPriceRow>
|
||||
{shouldShowUserListedPrice && asset.floor_sell_order_price
|
||||
? `${floorFormatter(asset.floor_sell_order_price)} ETH`
|
||||
: ' '}
|
||||
</TruncatedTextRow>
|
||||
</Box>
|
||||
</FloorPriceRow>
|
||||
</PrimaryRowContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const PrimaryRow = ({ children }: { children: ReactNode }) => (
|
||||
<Row gap="8" justifyContent="space-between">
|
||||
{children}
|
||||
</Row>
|
||||
)
|
||||
const StyledPrimaryRow = styled(Row)`
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const PrimaryRow = ({ children }: { children: ReactNode }) => <StyledPrimaryRow>{children}</StyledPrimaryRow>
|
||||
|
||||
const StyledPrimaryDetails = styled(Row)`
|
||||
justify-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const PrimaryDetails = ({ children }: { children: ReactNode }) => (
|
||||
<Row justifyItems="center" overflow="hidden" whiteSpace="nowrap">
|
||||
{children}
|
||||
</Row>
|
||||
<StyledPrimaryDetails>{children}</StyledPrimaryDetails>
|
||||
)
|
||||
|
||||
const PrimaryInfoContainer = styled.div`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
`
|
||||
|
||||
const PrimaryInfo = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<Box overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" className={body}>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
return <PrimaryInfoContainer>{children}</PrimaryInfoContainer>
|
||||
}
|
||||
|
||||
const SecondaryRow = ({ children }: { children: ReactNode }) => (
|
||||
<Row height="20" justifyContent="space-between" marginTop="6">
|
||||
{children}
|
||||
</Row>
|
||||
)
|
||||
const StyledSecondaryRow = styled(Row)`
|
||||
height: 20px;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
`
|
||||
|
||||
const SecondaryRow = ({ children }: { children: ReactNode }) => <StyledSecondaryRow>{children}</StyledSecondaryRow>
|
||||
|
||||
const StyledSecondaryDetails = styled(Row)`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const SecondaryDetails = ({ children }: { children: ReactNode }) => (
|
||||
<Row overflow="hidden" whiteSpace="nowrap">
|
||||
{children}
|
||||
</Row>
|
||||
<StyledSecondaryDetails>{children}</StyledSecondaryDetails>
|
||||
)
|
||||
|
||||
const SecondaryInfoContainer = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
const SecondaryInfo = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<Box
|
||||
color="textPrimary"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
style={{ lineHeight: '20px' }}
|
||||
className={subhead}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
return <SecondaryInfoContainer>{children}</SecondaryInfoContainer>
|
||||
}
|
||||
|
||||
const TertiaryInfoContainer = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
const TertiaryInfo = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<Box marginTop="8" color="textSecondary">
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
return <TertiaryInfoContainer>{children}</TertiaryInfoContainer>
|
||||
}
|
||||
|
||||
interface Erc1155ControlsInterface {
|
||||
@@ -700,15 +761,18 @@ const Erc1155Controls = ({ quantity }: Erc1155ControlsInterface) => {
|
||||
)
|
||||
}
|
||||
|
||||
const StyledMarketplaceIcon = styled.img`
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
vertical-align: top;
|
||||
`
|
||||
|
||||
const MarketplaceIcon = ({ marketplace }: { marketplace: string }) => {
|
||||
return (
|
||||
<Box
|
||||
as="img"
|
||||
alt={marketplace}
|
||||
src={`/nft/svgs/marketplaces/${marketplace}.svg`}
|
||||
className={styles.marketplaceIcon}
|
||||
/>
|
||||
)
|
||||
return <StyledMarketplaceIcon alt={marketplace} src={`/nft/svgs/marketplaces/${marketplace}.svg`} />
|
||||
}
|
||||
|
||||
const DetailsLink = () => {
|
||||
@@ -721,7 +785,7 @@ const DetailsLink = () => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Box data-testid="nft-details-link">Details</Box>
|
||||
<div data-testid="nft-details-link">Details</div>
|
||||
</DetailsLinkContainer>
|
||||
)
|
||||
}
|
||||
@@ -734,6 +798,28 @@ interface RankingProps {
|
||||
rarityLogo?: string
|
||||
}
|
||||
|
||||
const RarityLogoContainer = styled(Row)`
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
`
|
||||
|
||||
const RarityText = styled(ThemedText.BodySmall)`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const RarityInfo = styled(Row)`
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 0px 4px;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0.04em;
|
||||
backdrop-filter: blur(6px);
|
||||
`
|
||||
|
||||
const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps) => {
|
||||
const { asset } = useCardContext()
|
||||
|
||||
@@ -744,44 +830,49 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Row>
|
||||
<Box display="flex" marginRight="4">
|
||||
<img src={rarityLogo} alt="cardLogo" width={16} />
|
||||
</Box>
|
||||
<Box width="full" className={bodySmall}>
|
||||
<RarityLogoContainer>
|
||||
<img src={rarityLogo} alt="cardLogo" width={16} height={16} />
|
||||
</RarityLogoContainer>
|
||||
<RarityText>
|
||||
{rarityVerified
|
||||
? `Verified by ${
|
||||
('collectionName' in asset && asset.collectionName) ||
|
||||
('asset_contract' in asset && asset.asset_contract?.name)
|
||||
}`
|
||||
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`}
|
||||
</Box>
|
||||
</RarityText>
|
||||
</Row>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Box className={styles.rarityInfo}>
|
||||
<Box paddingTop="2" paddingBottom="2" display="flex">
|
||||
{putCommas(provider.rank)}
|
||||
</Box>
|
||||
|
||||
<Box display="flex" height="16">
|
||||
{rarityVerified ? <RarityVerifiedIcon /> : null}
|
||||
</Box>
|
||||
</Box>
|
||||
<RarityInfo>
|
||||
<Row padding="2px 0px">{putCommas(provider.rank)}</Row>
|
||||
<Row>{rarityVerified ? <RarityVerifiedIcon /> : null}</Row>
|
||||
</RarityInfo>
|
||||
</MouseoverTooltip>
|
||||
</RankingContainer>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
const SUSPICIOUS_TEXT = 'Blocked on OpenSea'
|
||||
|
||||
const SUSPICIOUS_TEXT = t`Blocked on OpenSea`
|
||||
|
||||
const SuspiciousIconContainer = styled(Row)`
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
`
|
||||
|
||||
const PoolIconContainer = styled(SuspiciousIconContainer)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const Suspicious = () => {
|
||||
return (
|
||||
<MouseoverTooltip text={<Box className={bodySmall}>{SUSPICIOUS_TEXT}</Box>} placement="top">
|
||||
<Box display="flex" flexShrink="0" marginLeft="4">
|
||||
<MouseoverTooltip text={<ThemedText.BodySmall>{SUSPICIOUS_TEXT}</ThemedText.BodySmall>} placement="top">
|
||||
<SuspiciousIconContainer>
|
||||
<SuspiciousIcon />
|
||||
</Box>
|
||||
</SuspiciousIconContainer>
|
||||
</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
@@ -790,44 +881,46 @@ const Pool = () => {
|
||||
return (
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Box className={bodySmall}>
|
||||
<ThemedText.BodySmall>
|
||||
This NFT is part of a liquidity pool. Buying this will increase the price of the remaining pooled NFTs.
|
||||
</Box>
|
||||
</ThemedText.BodySmall>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Box display="flex" flexShrink="0" marginLeft="4" color="textSecondary">
|
||||
<PoolIconContainer>
|
||||
<PoolIcon width="20" height="20" />
|
||||
</Box>
|
||||
</PoolIconContainer>
|
||||
</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const NoContentContainerBackground = styled.div<{ height?: number }>`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: ${({ height }) => (height ? `${height}px` : 'auto')};
|
||||
padding-top: 100%;
|
||||
background: ${({ theme }) =>
|
||||
`linear-gradient(90deg, ${theme.backgroundSurface} 0%, ${theme.backgroundInteractive} 95.83%)`};
|
||||
`
|
||||
|
||||
const NoContentText = styled(ThemedText.BodyPrimary)`
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
color: ${colors.gray500};
|
||||
`
|
||||
|
||||
const NoContentContainer = ({ height }: { height?: number }) => (
|
||||
<>
|
||||
<Box
|
||||
position="relative"
|
||||
width="full"
|
||||
style={{
|
||||
height: height ? `${height}px` : 'auto',
|
||||
paddingTop: '100%',
|
||||
background: `linear-gradient(90deg, ${themeVars.colors.backgroundSurface} 0%, ${themeVars.colors.backgroundInteractive} 95.83%)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
textAlign="center"
|
||||
left="1/2"
|
||||
top="1/2"
|
||||
style={{ transform: 'translate3d(-50%, -50%, 0)' }}
|
||||
color="gray500"
|
||||
className={body}
|
||||
>
|
||||
Content not
|
||||
<NoContentContainerBackground height={height}>
|
||||
<NoContentText>
|
||||
<Trans>Content not</Trans>
|
||||
<br />
|
||||
available yet
|
||||
</Box>
|
||||
</Box>
|
||||
<Trans>available yet</Trans>
|
||||
</NoContentText>
|
||||
</NoContentContainerBackground>
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
@@ -240,21 +240,36 @@ export const RarityVerifiedIcon = () => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const EditPriceIcon = (props: SVGProps) => (
|
||||
<svg {...props} width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.4713 5.06496L13.2161 4.28041C13.5935 3.88317 13.6233 3.33696 13.2459 2.95958L12.9877 2.69144C12.65 2.35378 12.084 2.40344 11.7165 2.77089L10.9419 3.52565L12.4713 5.06496ZM3.10986 13.2347L5.14573 12.3806L11.7463 5.78L10.2169 4.26055L3.61635 10.8711L2.72255 12.8374C2.62324 13.0658 2.88145 13.324 3.10986 13.2347Z"
|
||||
fill="#70757A"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const AttachPriceIcon = (props: SVGProps) => (
|
||||
<svg {...props} width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.76353 9.88671L8.53195 9.10825C7.93931 9.05803 7.51242 8.86216 7.20605 8.5558C6.35728 7.70702 6.35728 6.50669 7.20103 5.66796L8.86844 3.99553C9.71722 3.15178 10.9125 3.14676 11.7613 3.99553C12.6151 4.84932 12.6051 6.04464 11.7663 6.88839L10.9125 7.73716C11.0732 8.10881 11.1285 8.56082 11.0381 8.95256L12.4745 7.5212C13.71 6.29073 13.715 4.53292 12.4694 3.28738C11.2189 2.03682 9.47112 2.04687 8.23563 3.28236L6.48786 5.03013C5.25237 6.26562 5.24735 8.01841 6.49289 9.26394C6.78418 9.56026 7.18597 9.78124 7.76353 9.88671ZM8.23061 5.64285L7.46219 6.42131C8.05483 6.47655 8.48172 6.6674 8.78809 6.97376C9.64188 7.82254 9.63686 9.02287 8.79311 9.86662L7.1257 11.534C6.27693 12.3828 5.08161 12.3828 4.23786 11.534C3.38407 10.6802 3.38909 9.48995 4.23284 8.6462L5.08161 7.7924C4.9209 7.42577 4.87068 6.97376 4.95605 6.577L3.51967 8.01339C2.28418 9.24385 2.27916 10.9966 3.52469 12.2422C4.77525 13.4927 6.52302 13.4827 7.75851 12.2522L9.50628 10.4994C10.7418 9.26394 10.7468 7.51115 9.50126 6.26562C9.20996 5.97432 8.8132 5.75334 8.23061 5.64285Z"
|
||||
fill="#4673FA"
|
||||
/>
|
||||
export const BrokenLinkIcon = (props: SVGProps) => (
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<g clipPath="url(#clip0_79_4612)">
|
||||
<path
|
||||
d="M14.4344 11.3181L16.9344 8.81813C17.6934 8.03229 18.1133 6.97978 18.1039 5.8873C18.0944 4.79481 17.6562 3.74976 16.8836 2.97722C16.1111 2.20469 15.066 1.76649 13.9735 1.75699C12.8811 1.7475 11.8286 2.16748 11.0427 2.92647L9.60938 4.35147"
|
||||
stroke="#98A1C0"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.20088 8.75098L2.70088 11.251C1.94189 12.0368 1.52191 13.0893 1.53141 14.1818C1.5409 15.2743 1.9791 16.3194 2.75164 17.0919C3.52417 17.8644 4.56922 18.3026 5.66171 18.3121C6.7542 18.3216 7.80671 17.9016 8.59255 17.1426L10.0175 15.7176"
|
||||
stroke="#98A1C0"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5 3.24316L14.7368 16.6952"
|
||||
stroke="#98A1C0"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_79_4612">
|
||||
<rect width="20" height="20" fill="white" transform="translate(0.128906 0.0341797)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -289,30 +304,6 @@ export const ClockIcon = () => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const LoadingIcon = (props: SVGProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="100px"
|
||||
height="100px"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<circle cx="50" cy="50" fill="none" strokeWidth="10" r="35" strokeDasharray="164.93361431346415 56.97787143782138">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
repeatCount="indefinite"
|
||||
dur="1s"
|
||||
values="0 50 50;360 50 50"
|
||||
keyTimes="0;1"
|
||||
{...props}
|
||||
></animateTransform>
|
||||
</circle>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const ApprovedCheckmarkIcon = (props: SVGProps) => (
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
@@ -322,25 +313,6 @@ export const ApprovedCheckmarkIcon = (props: SVGProps) => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const HazardIcon = () => (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="8.57227" y="6.66669" width="2.85714" height="8.57143" fill="white" />
|
||||
<path
|
||||
d="M5.14158 15.7143H14.8489C15.8017 15.7143 16.4294 15.0318 16.4294 14.1977C16.4294 13.9377 16.3509 13.6885 16.2052 13.4502L11.3516 5.05483C11.0489 4.53486 10.5221 4.28571 9.99523 4.28571C9.46839 4.28571 8.93034 4.53486 8.6389 5.05483L3.78524 13.4502C3.63952 13.6885 3.57227 13.9377 3.57227 14.1977C3.57227 15.0318 4.18878 15.7143 5.14158 15.7143ZM9.99523 11.4245C9.56928 11.4245 9.25542 11.1428 9.24421 10.7312L9.15453 7.71969C9.14332 7.24305 9.49081 6.90724 9.99523 6.90724C10.5109 6.90724 10.8584 7.23222 10.8471 7.71969L10.7575 10.7312C10.7351 11.1428 10.4324 11.4245 9.99523 11.4245ZM9.99523 14.0677C9.43477 14.0677 8.98639 13.6452 8.98639 13.1036C8.98639 12.5728 9.43477 12.1503 9.99523 12.1503C10.5669 12.1503 11.0153 12.5836 11.0153 13.1036C11.0041 13.6452 10.5669 14.0677 9.99523 14.0677Z"
|
||||
fill="#F95E14"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const FailedListingIcon = (props: SVGProps) => (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M9.9933 16.2444C13.5529 16.2444 16.4909 13.3064 16.4909 9.75307C16.4909 6.19978 13.5466 3.26172 9.98703 3.26172C6.43373 3.26172 3.50195 6.19978 3.50195 9.75307C3.50195 13.3064 6.44001 16.2444 9.9933 16.2444ZM8.12877 12.3207C7.78976 12.3207 7.62653 12.1324 7.62653 11.8624V7.63742C7.62653 7.36747 7.78976 7.17913 8.12877 7.17913H8.80678C9.14579 7.17913 9.30901 7.36747 9.30901 7.63742V11.8624C9.30901 12.1324 9.14579 12.3207 8.80678 12.3207H8.12877ZM11.1798 12.3207C10.8471 12.3207 10.6776 12.1324 10.6776 11.8624V7.63742C10.6776 7.36747 10.8471 7.17913 11.1798 7.17913H11.8641C12.1906 7.17913 12.3538 7.36747 12.3538 7.63742V11.8624C12.3538 12.1324 12.1906 12.3207 11.8641 12.3207H11.1798Z"
|
||||
fill={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const FilterIcon = (props: SVGProps) => (
|
||||
<svg width="20" height="20" viewBox="1 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
|
||||
@@ -16,6 +16,6 @@ export const overlay = style([
|
||||
{
|
||||
opacity: 0.72,
|
||||
overflow: 'hidden',
|
||||
zIndex: Z_INDEX.modalBackdrop + 1,
|
||||
zIndex: Z_INDEX.modalBackdrop - 2,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { SMALL_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { ListingButton } from 'nft/components/bag/profile/ListingButton'
|
||||
import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils'
|
||||
import { useBag, useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { ListingButton } from 'nft/components/profile/list/ListingButton'
|
||||
import {
|
||||
approveCollectionRow,
|
||||
getTotalEthValue,
|
||||
useSubscribeListingState,
|
||||
verifyStatus,
|
||||
} from 'nft/components/profile/list/utils'
|
||||
import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
|
||||
import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
|
||||
import { looksRareNonceFetcher } from 'nft/queries'
|
||||
import { ListingStatus, ProfilePageStateType } from 'nft/types'
|
||||
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { formatEth } from 'nft/utils'
|
||||
import { ListingMarkets } from 'nft/utils/listNfts'
|
||||
import { useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import shallow from 'zustand/shallow'
|
||||
@@ -81,20 +88,11 @@ const ButtonsWrapper = styled(Row)`
|
||||
width: min-content;
|
||||
`
|
||||
|
||||
const MarketWrap = styled.section<{ isNftListV2: boolean }>`
|
||||
const MarketWrap = styled.section`
|
||||
gap: 48px;
|
||||
margin: 0px auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
${({ isNftListV2 }) => !isNftListV2 && v1Padding}
|
||||
`
|
||||
|
||||
const v1Padding = css`
|
||||
padding: 0px 16px;
|
||||
|
||||
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
padding: 0px 44px;
|
||||
}
|
||||
`
|
||||
|
||||
const ListingHeaderRow = styled(Row)`
|
||||
@@ -112,15 +110,6 @@ const GridWrapper = styled.div`
|
||||
margin-bottom: 48px;
|
||||
`
|
||||
|
||||
const MobileListButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
margin: 14px 16px 32px 16px;
|
||||
|
||||
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const FloatingConfirmationBar = styled(Row)<{ issues: boolean }>`
|
||||
padding: 12px 12px 12px 32px;
|
||||
border: 1px solid;
|
||||
@@ -195,9 +184,7 @@ const EthValueWrapper = styled.span<{ totalEthListingValue: boolean }>`
|
||||
export const ListPage = () => {
|
||||
const { setProfilePageState: setSellPageState } = useProfilePageState()
|
||||
const { provider } = useWeb3React()
|
||||
const toggleBag = useBag((s) => s.toggleBag)
|
||||
const isMobile = useIsMobile()
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
|
||||
const { setGlobalMarketplaces, sellAssets, issues } = useSellAsset(
|
||||
({ setGlobalMarketplaces, sellAssets, issues }) => ({
|
||||
@@ -207,26 +194,10 @@ export const ListPage = () => {
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const {
|
||||
listings,
|
||||
collectionsRequiringApproval,
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setLooksRareNonce,
|
||||
setCollectionStatusAndCallback,
|
||||
} = useNFTList(
|
||||
({
|
||||
const { listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback } = useNFTList(
|
||||
({ listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback }) => ({
|
||||
listings,
|
||||
collectionsRequiringApproval,
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setLooksRareNonce,
|
||||
setCollectionStatusAndCallback,
|
||||
}) => ({
|
||||
listings,
|
||||
collectionsRequiringApproval,
|
||||
listingStatus,
|
||||
setListingStatus,
|
||||
setLooksRareNonce,
|
||||
setCollectionStatusAndCallback,
|
||||
}),
|
||||
@@ -234,32 +205,16 @@ export const ListPage = () => {
|
||||
)
|
||||
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
const anyListingsMissingPrice = useMemo(() => !!listings.find((listing) => !listing.price), [listings])
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
|
||||
const usdcValue = useStablecoinValue(parsedAmount)
|
||||
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
|
||||
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
|
||||
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
|
||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
||||
const signer = provider?.getSigner()
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrice().then((price) => {
|
||||
setEthPriceInUSD(price ?? 0)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// TODO with removal of list v1 see if this logic can be removed
|
||||
useEffect(() => {
|
||||
const state = getListingState(collectionsRequiringApproval, listings)
|
||||
|
||||
if (state.allListingsApproved) setListingStatus(ListingStatus.APPROVED)
|
||||
else if (state.anyPaused && !state.anyActiveFailures && !state.anyActiveSigning && !state.anyActiveRejections) {
|
||||
setListingStatus(ListingStatus.CONTINUE)
|
||||
} else if (state.anyPaused) setListingStatus(ListingStatus.PAUSED)
|
||||
else if (state.anyActiveSigning) setListingStatus(ListingStatus.SIGNING)
|
||||
else if (state.allListingsPending || (state.allCollectionsPending && state.allListingsDefined))
|
||||
setListingStatus(ListingStatus.PENDING)
|
||||
else if (state.anyActiveFailures && listingStatus !== ListingStatus.PAUSED) setListingStatus(ListingStatus.FAILED)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listings, collectionsRequiringApproval])
|
||||
// instantiate listings and collections to approve when users modify input data
|
||||
useSubscribeListingState()
|
||||
|
||||
useEffect(() => {
|
||||
setGlobalMarketplaces(selectedMarkets)
|
||||
@@ -270,14 +225,13 @@ export const ListPage = () => {
|
||||
token_ids: sellAssets.map((asset) => asset.tokenId),
|
||||
marketplaces: Array.from(new Set(listings.map((asset) => asset.marketplace.name))),
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
usd_value: usdcAmount,
|
||||
...trace,
|
||||
}
|
||||
|
||||
const startListingFlow = async () => {
|
||||
if (!signer) return
|
||||
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
|
||||
setListingStatus(ListingStatus.SIGNING)
|
||||
const signerAddress = await signer.getAddress()
|
||||
const nonce = await looksRareNonceFetcher(signerAddress)
|
||||
setLooksRareNonce(nonce ?? 0)
|
||||
@@ -291,7 +245,7 @@ export const ListPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleV2Click = () => {
|
||||
const showModalAndStartListing = () => {
|
||||
toggleShowListModal()
|
||||
startListingFlow()
|
||||
}
|
||||
@@ -308,7 +262,7 @@ export const ListPage = () => {
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<MarketWrap isNftListV2={isNftListV2}>
|
||||
<MarketWrap>
|
||||
<ListingHeader>
|
||||
<Row>
|
||||
<ArrowContainer>
|
||||
@@ -332,39 +286,22 @@ export const ListPage = () => {
|
||||
<NFTListingsGrid selectedMarkets={selectedMarkets} />
|
||||
</GridWrapper>
|
||||
</MarketWrap>
|
||||
{isNftListV2 && (
|
||||
<>
|
||||
<FloatingConfirmationBar issues={!!issues}>
|
||||
{BannerText}
|
||||
<ProceedsAndButtonWrapper>
|
||||
<ProceedsWrapper>
|
||||
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
|
||||
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH
|
||||
</EthValueWrapper>
|
||||
{!!totalEthListingValue && !!ethPriceInUSD && (
|
||||
<UsdValue>{formatUsdPrice(totalEthListingValue * ethPriceInUSD)}</UsdValue>
|
||||
)}
|
||||
</ProceedsWrapper>
|
||||
<ListingButton
|
||||
onClick={handleV2Click}
|
||||
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
|
||||
showWarningOverride={true}
|
||||
/>
|
||||
</ProceedsAndButtonWrapper>
|
||||
</FloatingConfirmationBar>
|
||||
<Overlay />
|
||||
</>
|
||||
)}
|
||||
{!isNftListV2 && (
|
||||
<MobileListButtonWrapper>
|
||||
<ListingButton onClick={toggleBag} buttonText="Continue listing" />
|
||||
</MobileListButtonWrapper>
|
||||
)}
|
||||
{isNftListV2 && showListModal && (
|
||||
<>
|
||||
<ListModal overlayClick={toggleShowListModal} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<FloatingConfirmationBar issues={!!issues}>
|
||||
{BannerText}
|
||||
<ProceedsAndButtonWrapper>
|
||||
<ProceedsWrapper>
|
||||
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
|
||||
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH
|
||||
</EthValueWrapper>
|
||||
{!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>}
|
||||
</ProceedsWrapper>
|
||||
<ListingButton onClick={showModalAndStartListing} />
|
||||
</ProceedsAndButtonWrapper>
|
||||
</FloatingConfirmationBar>
|
||||
<Overlay />
|
||||
|
||||
{showListModal && <ListModal overlayClick={toggleShowListModal} />}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
126
src/nft/components/profile/list/ListingButton.tsx
Normal file
126
src/nft/components/profile/list/ListingButton.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Plural, t, Trans } from '@lingui/macro'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import ms from 'ms.macro'
|
||||
import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal'
|
||||
import { useIsMobile, useSellAsset } from 'nft/hooks'
|
||||
import { Listing, WalletAsset } from 'nft/types'
|
||||
import { useMemo, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
|
||||
|
||||
const StyledListingButton = styled(BaseButton)<{ showResolveIssues: boolean; missingPrices: boolean }>`
|
||||
background: ${({ showResolveIssues, theme }) => (showResolveIssues ? theme.accentFailure : theme.accentAction)};
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
width: min-content;
|
||||
border: none;
|
||||
cursor: ${({ missingPrices }) => (missingPrices ? 'auto' : 'pointer')};
|
||||
opacity: ${({ showResolveIssues, missingPrices }) => !showResolveIssues && missingPrices && '0.3'};
|
||||
|
||||
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
`
|
||||
|
||||
export const ListingButton = ({ onClick }: { onClick: () => void }) => {
|
||||
const { sellAssets, showResolveIssues, toggleShowResolveIssues, issues, setIssues } = useSellAsset(
|
||||
({ sellAssets, showResolveIssues, toggleShowResolveIssues, issues, setIssues }) => ({
|
||||
sellAssets,
|
||||
showResolveIssues,
|
||||
toggleShowResolveIssues,
|
||||
issues,
|
||||
setIssues,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const [showWarning, setShowWarning] = useState(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
// Find issues with item listing data
|
||||
const [listingsMissingPrice, listingsBelowFloor] = useMemo(() => {
|
||||
const missingExpiration = sellAssets.some((asset) => {
|
||||
return (
|
||||
asset.expirationTime != null &&
|
||||
(isNaN(asset.expirationTime) || asset.expirationTime * 1000 - Date.now() < ms`60 seconds`)
|
||||
)
|
||||
})
|
||||
const overMaxExpiration = sellAssets.some((asset) => {
|
||||
return asset.expirationTime != null && asset.expirationTime * 1000 - Date.now() > ms`180 days`
|
||||
})
|
||||
|
||||
const listingsMissingPrice: [WalletAsset, Listing][] = []
|
||||
const listingsBelowFloor: [WalletAsset, Listing][] = []
|
||||
const listingsAboveSellOrderFloor: [WalletAsset, Listing][] = []
|
||||
const invalidPrices: [WalletAsset, Listing][] = []
|
||||
for (const asset of sellAssets) {
|
||||
if (asset.newListings) {
|
||||
for (const listing of asset.newListings) {
|
||||
if (!listing.price) listingsMissingPrice.push([asset, listing])
|
||||
else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing])
|
||||
else if (
|
||||
listing.price < (asset?.floorPrice ?? 0) * BELOW_FLOOR_PRICE_THRESHOLD &&
|
||||
!listing.overrideFloorPrice
|
||||
)
|
||||
listingsBelowFloor.push([asset, listing])
|
||||
else if (asset.floor_sell_order_price && listing.price >= asset.floor_sell_order_price)
|
||||
listingsAboveSellOrderFloor.push([asset, listing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set number of issues
|
||||
const foundIssues =
|
||||
Number(missingExpiration) +
|
||||
Number(overMaxExpiration) +
|
||||
listingsMissingPrice.length +
|
||||
listingsAboveSellOrderFloor.length
|
||||
setIssues(foundIssues)
|
||||
!foundIssues && showResolveIssues && toggleShowResolveIssues()
|
||||
// Only show Resolve Issue text if there was a user submitted error (ie not when page loads with no prices set)
|
||||
if ((missingExpiration || overMaxExpiration || listingsAboveSellOrderFloor.length) && !showResolveIssues)
|
||||
toggleShowResolveIssues()
|
||||
|
||||
return [listingsMissingPrice, listingsBelowFloor]
|
||||
}, [sellAssets, setIssues, showResolveIssues, toggleShowResolveIssues])
|
||||
|
||||
const warningWrappedClick = () => {
|
||||
if (issues) !showResolveIssues && toggleShowResolveIssues()
|
||||
else if (listingsBelowFloor.length) setShowWarning(true)
|
||||
else onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledListingButton
|
||||
onClick={warningWrappedClick}
|
||||
missingPrices={!!listingsMissingPrice.length}
|
||||
showResolveIssues={showResolveIssues}
|
||||
>
|
||||
{showResolveIssues ? (
|
||||
<Plural value={issues !== 1 ? 2 : 1} _1="Resolve issue" other={t`Resolve ${issues} issues`} />
|
||||
) : listingsMissingPrice.length && !isMobile ? (
|
||||
<Trans>Set prices to continue</Trans>
|
||||
) : (
|
||||
<Trans>Start listing</Trans>
|
||||
)}
|
||||
</StyledListingButton>
|
||||
|
||||
{showWarning && (
|
||||
<BelowFloorWarningModal
|
||||
listingsBelowFloor={listingsBelowFloor}
|
||||
closeModal={() => setShowWarning(false)}
|
||||
startListing={onClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -4,19 +4,18 @@ import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { RowsCollpsedIcon, RowsExpandedIcon } from 'nft/components/icons'
|
||||
import { getRoyalty, useHandleGlobalPriceToggle, useSyncPriceWithGlobalMethod } from 'nft/components/profile/list/utils'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { ListingMarket, ListingWarning, WalletAsset } from 'nft/types'
|
||||
import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
|
||||
import { ListingMarket, WalletAsset } from 'nft/types'
|
||||
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { Dispatch, DispatchWithoutAction, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { Dispatch, DispatchWithoutAction, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
|
||||
import { SetPriceMethod } from './NFTListingsGrid'
|
||||
import { PriceTextInput } from './PriceTextInput'
|
||||
import { RoyaltyTooltip } from './RoyaltyTooltip'
|
||||
import { RemoveIconWrap } from './shared'
|
||||
import { RemoveIconWrap, SetPriceMethod } from './shared'
|
||||
|
||||
const LastPriceInfo = styled(Column)`
|
||||
text-align: left;
|
||||
@@ -104,13 +103,6 @@ const ReturnColumn = styled(Column)`
|
||||
}
|
||||
`
|
||||
|
||||
const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
|
||||
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
|
||||
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
|
||||
|
||||
return baseFee * 0.01
|
||||
}
|
||||
|
||||
interface MarketplaceRowProps {
|
||||
globalPriceMethod?: SetPriceMethod
|
||||
globalPrice?: number
|
||||
@@ -118,7 +110,6 @@ interface MarketplaceRowProps {
|
||||
selectedMarkets: ListingMarket[]
|
||||
removeMarket?: () => void
|
||||
asset: WalletAsset
|
||||
showMarketplaceLogo: boolean
|
||||
expandMarketplaceRows?: boolean
|
||||
rowHovered?: boolean
|
||||
toggleExpandMarketplaceRows: DispatchWithoutAction
|
||||
@@ -131,20 +122,31 @@ export const MarketplaceRow = ({
|
||||
selectedMarkets,
|
||||
removeMarket = undefined,
|
||||
asset,
|
||||
showMarketplaceLogo,
|
||||
expandMarketplaceRows,
|
||||
toggleExpandMarketplaceRows,
|
||||
rowHovered,
|
||||
}: MarketplaceRowProps) => {
|
||||
const [listPrice, setListPrice] = useState<number>()
|
||||
const [globalOverride, setGlobalOverride] = useState(false)
|
||||
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
|
||||
const setAssetListPrice = useSellAsset((state) => state.setAssetListPrice)
|
||||
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
|
||||
const [marketIconHovered, toggleMarketIconHovered] = useReducer((s) => !s, false)
|
||||
const [marketRowHovered, toggleMarketRowHovered] = useReducer((s) => !s, false)
|
||||
const [listPrice, setListPrice] = useState<number | undefined>(
|
||||
() =>
|
||||
asset.newListings?.find((listing) =>
|
||||
expandMarketplaceRows ? listing.marketplace.name === selectedMarkets?.[0].name : !!listing.price
|
||||
)?.price
|
||||
)
|
||||
const [globalOverride, setGlobalOverride] = useState(false)
|
||||
|
||||
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride
|
||||
const price = showGlobalPrice ? globalPrice : listPrice
|
||||
const setPrice = useCallback(
|
||||
(price?: number) => {
|
||||
showGlobalPrice ? setGlobalPrice(price) : setListPrice(price)
|
||||
for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
|
||||
},
|
||||
[asset, selectedMarkets, setAssetListPrice, setGlobalPrice, showGlobalPrice]
|
||||
)
|
||||
|
||||
const fees = useMemo(() => {
|
||||
if (selectedMarkets.length === 1) {
|
||||
@@ -163,79 +165,36 @@ export const MarketplaceRow = ({
|
||||
const feeInEth = price && (price * fees) / 100
|
||||
const userReceives = price && feeInEth && price - feeInEth
|
||||
|
||||
useMemo(() => {
|
||||
for (const market of selectedMarkets) {
|
||||
if (market && asset && asset.basisPoints) {
|
||||
market.royalty = (market.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints) * 0.01
|
||||
}
|
||||
}
|
||||
}, [asset, selectedMarkets])
|
||||
useHandleGlobalPriceToggle(globalOverride, setListPrice, setPrice, listPrice, globalPrice)
|
||||
useSyncPriceWithGlobalMethod(
|
||||
asset,
|
||||
setListPrice,
|
||||
setGlobalPrice,
|
||||
setGlobalOverride,
|
||||
listPrice,
|
||||
globalPrice,
|
||||
globalPriceMethod
|
||||
)
|
||||
|
||||
// When in Same Price Mode and not overriding, update local price when global price changes
|
||||
useEffect(() => {
|
||||
if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) {
|
||||
setListPrice(asset?.floorPrice)
|
||||
setGlobalPrice(asset.floorPrice)
|
||||
} else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
|
||||
setListPrice(asset.lastPrice)
|
||||
setGlobalPrice(asset.lastPrice)
|
||||
} else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
|
||||
listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
|
||||
|
||||
setGlobalOverride(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [globalPriceMethod])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedMarkets.length)
|
||||
for (const marketplace of selectedMarkets) setAssetListPrice(asset, listPrice, marketplace)
|
||||
else setAssetListPrice(asset, listPrice)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPrice])
|
||||
|
||||
useEffect(() => {
|
||||
let price: number | undefined = undefined
|
||||
if (globalOverride) {
|
||||
if (!listPrice) setListPrice(globalPrice)
|
||||
price = listPrice ? listPrice : globalPrice
|
||||
} else {
|
||||
price = globalPrice
|
||||
}
|
||||
if (selectedMarkets.length) for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
|
||||
else setAssetListPrice(asset, price)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [globalOverride])
|
||||
|
||||
useEffect(() => {
|
||||
if (globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride) {
|
||||
if (selectedMarkets.length)
|
||||
for (const marketplace of selectedMarkets) setAssetListPrice(asset, globalPrice, marketplace)
|
||||
else setAssetListPrice(asset, globalPrice)
|
||||
if (showGlobalPrice) {
|
||||
setPrice(globalPrice)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [globalPrice])
|
||||
|
||||
let warning: ListingWarning | undefined = undefined
|
||||
if (asset.listingWarnings && asset.listingWarnings?.length > 0) {
|
||||
if (showMarketplaceLogo) {
|
||||
for (const listingWarning of asset.listingWarnings) {
|
||||
if (listingWarning.marketplace.name === selectedMarkets[0].name) warning = listingWarning
|
||||
}
|
||||
} else {
|
||||
warning = asset.listingWarnings[0]
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
|
||||
<FloorPriceInfo>
|
||||
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
|
||||
<ThemedText.BodyPrimary color="textSecondary" lineHeight="24px">
|
||||
{asset.floorPrice ? `${asset.floorPrice.toFixed(3)} ETH` : '-'}
|
||||
</ThemedText.BodySmall>
|
||||
</ThemedText.BodyPrimary>
|
||||
</FloorPriceInfo>
|
||||
<LastPriceInfo>
|
||||
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
|
||||
<ThemedText.BodyPrimary color="textSecondary" lineHeight="24px">
|
||||
{asset.lastPrice ? `${asset.lastPrice.toFixed(3)} ETH` : '-'}
|
||||
</ThemedText.BodySmall>
|
||||
</ThemedText.BodyPrimary>
|
||||
</LastPriceInfo>
|
||||
|
||||
<Row flex="2">
|
||||
@@ -258,27 +217,14 @@ export const MarketplaceRow = ({
|
||||
))}
|
||||
</MarketIconsWrapper>
|
||||
)}
|
||||
{globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride ? (
|
||||
<PriceTextInput
|
||||
listPrice={globalPrice}
|
||||
setListPrice={setGlobalPrice}
|
||||
isGlobalPrice={true}
|
||||
setGlobalOverride={setGlobalOverride}
|
||||
globalOverride={globalOverride}
|
||||
warning={warning}
|
||||
asset={asset}
|
||||
/>
|
||||
) : (
|
||||
<PriceTextInput
|
||||
listPrice={listPrice}
|
||||
setListPrice={setListPrice}
|
||||
isGlobalPrice={false}
|
||||
setGlobalOverride={setGlobalOverride}
|
||||
globalOverride={globalOverride}
|
||||
warning={warning}
|
||||
asset={asset}
|
||||
/>
|
||||
)}
|
||||
<PriceTextInput
|
||||
listPrice={price}
|
||||
setListPrice={setPrice}
|
||||
isGlobalPrice={showGlobalPrice}
|
||||
setGlobalOverride={setGlobalOverride}
|
||||
globalOverride={globalOverride}
|
||||
asset={asset}
|
||||
/>
|
||||
{rowHovered && ((expandMarketplaceRows && marketRowHovered) || selectedMarkets.length > 1) && (
|
||||
<ExpandMarketIconWrapper onClick={toggleExpandMarketplaceRows}>
|
||||
{expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getTotalEthValue, signListingRow } from 'nft/components/bag/profile/utils'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { getTotalEthValue, signListingRow } from 'nft/components/profile/list/utils'
|
||||
import { useNFTList, useSellAsset } from 'nft/hooks'
|
||||
import { ListingStatus } from 'nft/types'
|
||||
import { fetchPrice } from 'nft/utils'
|
||||
import { useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useReducer } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
@@ -46,64 +49,60 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
const signer = provider?.getSigner()
|
||||
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const {
|
||||
listingStatus,
|
||||
setListingStatusAndCallback,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
} = useNFTList(
|
||||
({
|
||||
listingStatus,
|
||||
setListingStatusAndCallback,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}) => ({
|
||||
listingStatus,
|
||||
setListingStatusAndCallback,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const { setListingStatusAndCallback, setLooksRareNonce, getLooksRareNonce, collectionsRequiringApproval, listings } =
|
||||
useNFTList(
|
||||
({
|
||||
setListingStatusAndCallback,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}) => ({
|
||||
setListingStatusAndCallback,
|
||||
setLooksRareNonce,
|
||||
getLooksRareNonce,
|
||||
collectionsRequiringApproval,
|
||||
listings,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
const [openSection, toggleOpenSection] = useReducer(
|
||||
(s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE),
|
||||
Section.APPROVE
|
||||
)
|
||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrice().then((price) => {
|
||||
setEthPriceInUSD(price || 0)
|
||||
})
|
||||
}, [])
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
|
||||
const usdcValue = useStablecoinValue(parsedAmount)
|
||||
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
|
||||
|
||||
const allCollectionsApproved = useMemo(
|
||||
() => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED),
|
||||
[collectionsRequiringApproval]
|
||||
)
|
||||
|
||||
const allListingsApproved = useMemo(
|
||||
() => listings.every((listing) => listing.status === ListingStatus.APPROVED),
|
||||
[listings]
|
||||
)
|
||||
|
||||
const signListings = async () => {
|
||||
if (!signer || !provider) return
|
||||
// sign listings
|
||||
for (const listing of listings) {
|
||||
await signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
|
||||
}
|
||||
|
||||
sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, {
|
||||
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
usd_value: usdcAmount,
|
||||
...trace,
|
||||
})
|
||||
}
|
||||
|
||||
// Once all collections have been approved, go to next section and start signing listings
|
||||
useEffect(() => {
|
||||
if (allCollectionsApproved) {
|
||||
signListings()
|
||||
@@ -112,24 +111,28 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [allCollectionsApproved])
|
||||
|
||||
const closeModalOnClick = useCallback(() => {
|
||||
allListingsApproved ? window.location.reload() : overlayClick()
|
||||
}, [allListingsApproved, overlayClick])
|
||||
|
||||
// In the case that a user removes all listings via retry logic, close modal
|
||||
useEffect(() => {
|
||||
!listings.length && overlayClick()
|
||||
}, [listings, overlayClick])
|
||||
!listings.length && closeModalOnClick()
|
||||
}, [listings, closeModalOnClick])
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
||||
<ListModalWrapper>
|
||||
{listingStatus === ListingStatus.APPROVED ? (
|
||||
<SuccessScreen overlayClick={overlayClick} />
|
||||
{allListingsApproved ? (
|
||||
<SuccessScreen overlayClick={closeModalOnClick} />
|
||||
) : (
|
||||
<>
|
||||
<TitleRow>
|
||||
<ThemedText.HeadlineSmall lineHeight="28px">
|
||||
<Trans>List NFTs</Trans>
|
||||
</ThemedText.HeadlineSmall>
|
||||
<X size={24} cursor="pointer" onClick={overlayClick} />
|
||||
<X size={24} cursor="pointer" onClick={closeModalOnClick} />
|
||||
</TitleRow>
|
||||
<ListModalSection
|
||||
sectionType={Section.APPROVE}
|
||||
@@ -147,7 +150,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
)}
|
||||
</ListModalWrapper>
|
||||
</Trace>
|
||||
<Overlay onClick={overlayClick} />
|
||||
<Overlay onClick={closeModalOnClick} />
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Row from 'components/Row'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { getTotalEthValue } from 'nft/components/bag/profile/utils'
|
||||
import { getTotalEthValue } from 'nft/components/profile/list/utils'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { formatEth, generateTweetForList, pluralize } from 'nft/utils'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
@@ -10,7 +10,9 @@ import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
import { opacify } from 'theme/utils'
|
||||
|
||||
import { MarketplaceRow } from './MarketplaceRow'
|
||||
import { SetPriceMethod } from './NFTListingsGrid'
|
||||
import { SetPriceMethod } from './shared'
|
||||
|
||||
const IMAGE_THUMBNAIL_SIZE = 60
|
||||
|
||||
const NFTListRowWrapper = styled(Row)`
|
||||
padding: 24px 0px;
|
||||
@@ -23,8 +25,8 @@ const NFTListRowWrapper = styled(Row)`
|
||||
`
|
||||
|
||||
const RemoveIconContainer = styled.div`
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: ${IMAGE_THUMBNAIL_SIZE}px;
|
||||
height: ${IMAGE_THUMBNAIL_SIZE}px;
|
||||
padding-left: 12px;
|
||||
align-self: flex-start;
|
||||
align-items: center;
|
||||
@@ -51,8 +53,8 @@ const NFTInfoWrapper = styled(Row)`
|
||||
`
|
||||
|
||||
const NFTImage = styled.img`
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: ${IMAGE_THUMBNAIL_SIZE}px;
|
||||
height: ${IMAGE_THUMBNAIL_SIZE}px;
|
||||
border-radius: 8px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
@@ -85,6 +87,7 @@ const MarketPlaceRowWrapper = styled(Column)`
|
||||
gap: 24px;
|
||||
flex: 1.5;
|
||||
margin-right: 12px;
|
||||
padding: 6px 0px;
|
||||
|
||||
@media screen and (min-width: ${BREAKPOINTS.md}px) {
|
||||
flex: 2;
|
||||
@@ -120,10 +123,10 @@ export const NFTListRow = ({
|
||||
const [hovered, toggleHovered] = useReducer((s) => !s, false)
|
||||
const theme = useTheme()
|
||||
|
||||
// Keep localMarkets up to date with changes to globalMarkets
|
||||
useEffect(() => {
|
||||
setLocalMarkets(JSON.parse(JSON.stringify(selectedMarkets)))
|
||||
selectedMarkets.length < 2 && expandMarketplaceRows && toggleExpandMarketplaceRows()
|
||||
}, [expandMarketplaceRows, selectedMarkets])
|
||||
}, [selectedMarkets])
|
||||
|
||||
return (
|
||||
<NFTListRowWrapper
|
||||
@@ -158,17 +161,16 @@ export const NFTListRow = ({
|
||||
</TokenInfoWrapper>
|
||||
</NFTInfoWrapper>
|
||||
<MarketPlaceRowWrapper>
|
||||
{expandMarketplaceRows ? (
|
||||
localMarkets.map((market, index) => {
|
||||
{expandMarketplaceRows && localMarkets.length > 1 ? (
|
||||
localMarkets.map((market) => {
|
||||
return (
|
||||
<MarketplaceRow
|
||||
globalPriceMethod={globalPriceMethod}
|
||||
globalPrice={globalPrice}
|
||||
setGlobalPrice={setGlobalPrice}
|
||||
selectedMarkets={[market]}
|
||||
removeMarket={() => localMarkets.splice(index, 1)}
|
||||
removeMarket={() => setLocalMarkets(localMarkets.filter((oldMarket) => oldMarket.name !== market.name))}
|
||||
asset={asset}
|
||||
showMarketplaceLogo={true}
|
||||
key={asset.name + market.name}
|
||||
expandMarketplaceRows={expandMarketplaceRows}
|
||||
rowHovered={hovered}
|
||||
@@ -183,7 +185,6 @@ export const NFTListRow = ({
|
||||
setGlobalPrice={setGlobalPrice}
|
||||
selectedMarkets={localMarkets}
|
||||
asset={asset}
|
||||
showMarketplaceLogo={false}
|
||||
rowHovered={hovered}
|
||||
toggleExpandMarketplaceRows={toggleExpandMarketplaceRows}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -12,6 +11,7 @@ import { BREAKPOINTS } from 'theme'
|
||||
|
||||
import { Dropdown } from './Dropdown'
|
||||
import { NFTListRow } from './NFTListRow'
|
||||
import { SetPriceMethod } from './shared'
|
||||
|
||||
const TableHeader = styled.div`
|
||||
display: flex;
|
||||
@@ -28,7 +28,7 @@ const TableHeader = styled.div`
|
||||
line-height: 20px;
|
||||
|
||||
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
|
||||
margin-left: 48px;
|
||||
padding-left: 48px;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -143,13 +143,6 @@ const RowDivider = styled.hr`
|
||||
border-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
`
|
||||
|
||||
export enum SetPriceMethod {
|
||||
SAME_PRICE,
|
||||
FLOOR_PRICE,
|
||||
LAST_PRICE,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingMarket[] }) => {
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const [globalPriceMethod, setGlobalPriceMethod] = useState(SetPriceMethod.CUSTOM)
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { AttachPriceIcon, EditPriceIcon } from 'nft/components/icons'
|
||||
import { BrokenLinkIcon } from 'nft/components/icons'
|
||||
import { NumericInput } from 'nft/components/layout/Input'
|
||||
import { useUpdateInputAndWarnings } from 'nft/components/profile/list/utils'
|
||||
import { body } from 'nft/css/common.css'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { ListingWarning, WalletAsset } from 'nft/types'
|
||||
import { WalletAsset } from 'nft/types'
|
||||
import { formatEth } from 'nft/utils/currency'
|
||||
import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Dispatch, useRef, useState } from 'react'
|
||||
import { AlertTriangle, Link } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import { WarningType } from './shared'
|
||||
|
||||
const PriceTextInputWrapper = styled(Column)`
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const InputWrapper = styled(Row)<{ borderColor: string }>`
|
||||
height: 44px;
|
||||
height: 48px;
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
padding: 12px;
|
||||
border: 2px solid;
|
||||
@@ -34,12 +37,17 @@ const CurrencyWrapper = styled.div<{ listPrice: number | undefined }>`
|
||||
`
|
||||
|
||||
const GlobalPriceIcon = styled.div`
|
||||
display: block;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -4px;
|
||||
bottom: 32px;
|
||||
right: -10px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 50%;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const WarningRow = styled(Row)`
|
||||
@@ -66,12 +74,6 @@ const WarningAction = styled.div`
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
|
||||
enum WarningType {
|
||||
BELOW_FLOOR,
|
||||
ALREADY_LISTED,
|
||||
NONE,
|
||||
}
|
||||
|
||||
const getWarningMessage = (warning: WarningType) => {
|
||||
let message = <></>
|
||||
switch (warning) {
|
||||
@@ -91,7 +93,6 @@ interface PriceTextInputProps {
|
||||
isGlobalPrice: boolean
|
||||
setGlobalOverride: Dispatch<boolean>
|
||||
globalOverride: boolean
|
||||
warning?: ListingWarning
|
||||
asset: WalletAsset
|
||||
}
|
||||
|
||||
@@ -101,43 +102,35 @@ export const PriceTextInput = ({
|
||||
isGlobalPrice,
|
||||
setGlobalOverride,
|
||||
globalOverride,
|
||||
warning,
|
||||
asset,
|
||||
}: PriceTextInputProps) => {
|
||||
const [focused, setFocused] = useState(false)
|
||||
const [warningType, setWarningType] = useState(WarningType.NONE)
|
||||
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
|
||||
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
|
||||
const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
|
||||
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>
|
||||
const theme = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current.value = listPrice !== undefined ? `${listPrice}` : ''
|
||||
setWarningType(WarningType.NONE)
|
||||
if (!warning && listPrice) {
|
||||
if (listPrice < (asset?.floorPrice ?? 0)) setWarningType(WarningType.BELOW_FLOOR)
|
||||
else if (asset.floor_sell_order_price && listPrice >= asset.floor_sell_order_price)
|
||||
setWarningType(WarningType.ALREADY_LISTED)
|
||||
} else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [listPrice])
|
||||
|
||||
const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100
|
||||
|
||||
const warningColor =
|
||||
showResolveIssues && !listPrice
|
||||
(showResolveIssues && !listPrice) ||
|
||||
warningType === WarningType.ALREADY_LISTED ||
|
||||
(warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20)
|
||||
? colors.red400
|
||||
: warningType !== WarningType.NONE && !focused
|
||||
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
|
||||
warningType === WarningType.ALREADY_LISTED
|
||||
? colors.red400
|
||||
: theme.accentWarning
|
||||
: isGlobalPrice
|
||||
: warningType === WarningType.BELOW_FLOOR
|
||||
? theme.accentWarning
|
||||
: isGlobalPrice || !!listPrice
|
||||
? theme.accentAction
|
||||
: listPrice != null
|
||||
? theme.textSecondary
|
||||
: theme.accentAction
|
||||
: theme.textSecondary
|
||||
|
||||
const setPrice = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!listPrice && event.target.value.includes('.') && parseFloat(event.target.value) === 0) {
|
||||
return
|
||||
}
|
||||
const val = parseFloat(event.target.value)
|
||||
setListPrice(isNaN(val) ? undefined : val)
|
||||
}
|
||||
|
||||
useUpdateInputAndWarnings(setWarningType, inputRef, asset, listPrice)
|
||||
|
||||
return (
|
||||
<PriceTextInputWrapper>
|
||||
@@ -151,48 +144,36 @@ export const PriceTextInput = ({
|
||||
placeholder="0"
|
||||
backgroundColor="none"
|
||||
width={{ sm: '54', md: '68' }}
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => {
|
||||
setFocused(false)
|
||||
}}
|
||||
ref={inputRef}
|
||||
onChange={(v: FormEvent<HTMLInputElement>) => {
|
||||
if (!listPrice && v.currentTarget.value.includes('.') && parseFloat(v.currentTarget.value) === 0) {
|
||||
return
|
||||
}
|
||||
const val = parseFloat(v.currentTarget.value)
|
||||
setListPrice(isNaN(val) ? undefined : val)
|
||||
}}
|
||||
onChange={setPrice}
|
||||
/>
|
||||
<CurrencyWrapper listPrice={listPrice}> ETH</CurrencyWrapper>
|
||||
{(isGlobalPrice || globalOverride) && (
|
||||
<GlobalPriceIcon onClick={() => setGlobalOverride(!globalOverride)}>
|
||||
{globalOverride ? <AttachPriceIcon /> : <EditPriceIcon />}
|
||||
{globalOverride ? <BrokenLinkIcon /> : <Link size={20} color={warningColor} />}
|
||||
</GlobalPriceIcon>
|
||||
)}
|
||||
</InputWrapper>
|
||||
<WarningMessage $color={warningColor}>
|
||||
{warning
|
||||
? warning.message
|
||||
: warningType !== WarningType.NONE && (
|
||||
<WarningRow>
|
||||
<AlertTriangle height={16} width={16} color={warningColor} />
|
||||
<span>
|
||||
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
|
||||
{getWarningMessage(warningType)}
|
||||
|
||||
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
|
||||
</span>
|
||||
<WarningAction
|
||||
onClick={() => {
|
||||
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
|
||||
setWarningType(WarningType.NONE)
|
||||
}}
|
||||
>
|
||||
{warningType === WarningType.BELOW_FLOOR ? <Trans>Dismiss</Trans> : <Trans>Remove item</Trans>}
|
||||
</WarningAction>
|
||||
</WarningRow>
|
||||
)}
|
||||
{warningType !== WarningType.NONE && (
|
||||
<WarningRow>
|
||||
<AlertTriangle height={16} width={16} color={warningColor} />
|
||||
<span>
|
||||
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
|
||||
{getWarningMessage(warningType)}
|
||||
|
||||
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
|
||||
</span>
|
||||
<WarningAction
|
||||
onClick={() => {
|
||||
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
|
||||
setWarningType(WarningType.NONE)
|
||||
}}
|
||||
>
|
||||
{warningType === WarningType.BELOW_FLOOR ? <Trans>Dismiss</Trans> : <Trans>Remove item</Trans>}
|
||||
</WarningAction>
|
||||
</WarningRow>
|
||||
)}
|
||||
</WarningMessage>
|
||||
</PriceTextInputWrapper>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { getRoyalty } from 'nft/components/profile/list/utils'
|
||||
import { ListingMarket, WalletAsset } from 'nft/types'
|
||||
import { formatEth } from 'nft/utils'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -50,7 +51,7 @@ export const RoyaltyTooltip = ({
|
||||
asset: WalletAsset
|
||||
fees?: number
|
||||
}) => {
|
||||
const maxRoyalty = Math.max(...selectedMarkets.map((market) => market.royalty ?? 0))
|
||||
const maxRoyalty = Math.max(...selectedMarkets.map((market) => getRoyalty(market, asset) ?? 0)).toFixed(2)
|
||||
return (
|
||||
<RoyaltyContainer>
|
||||
{selectedMarkets.map((market) => (
|
||||
|
||||
@@ -14,3 +14,16 @@ export const TitleRow = styled(Row)`
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
export enum SetPriceMethod {
|
||||
SAME_PRICE,
|
||||
FLOOR_PRICE,
|
||||
LAST_PRICE,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
export enum WarningType {
|
||||
BELOW_FLOOR,
|
||||
ALREADY_LISTED,
|
||||
NONE,
|
||||
}
|
||||
|
||||
196
src/nft/components/profile/list/utils.ts
Normal file
196
src/nft/components/profile/list/utils.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
|
||||
import { SetPriceMethod, WarningType } from 'nft/components/profile/list/shared'
|
||||
import { useNFTList, useSellAsset } from 'nft/hooks'
|
||||
import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries'
|
||||
import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
|
||||
import { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
|
||||
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
|
||||
import { Dispatch, useEffect } from 'react'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
export async function approveCollectionRow(
|
||||
collectionRow: CollectionRow,
|
||||
signer: JsonRpcSigner,
|
||||
setCollectionStatusAndCallback: (
|
||||
collection: CollectionRow,
|
||||
status: ListingStatus,
|
||||
callback?: () => Promise<void>
|
||||
) => void
|
||||
) {
|
||||
const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback)
|
||||
setCollectionStatusAndCallback(collectionRow, ListingStatus.SIGNING, callback)
|
||||
const { marketplace, collectionAddress } = collectionRow
|
||||
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
|
||||
const spender =
|
||||
marketplace.name === 'OpenSea'
|
||||
? OPENSEA_CROSS_CHAIN_CONDUIT
|
||||
: marketplace.name === 'Rarible'
|
||||
? LOOKSRARE_MARKETPLACE_CONTRACT
|
||||
: marketplace.name === 'X2Y2'
|
||||
? X2Y2_TRANSFER_CONTRACT
|
||||
: addresses.TRANSFER_MANAGER_ERC721
|
||||
!!collectionAddress &&
|
||||
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
|
||||
setCollectionStatusAndCallback(collectionRow, newStatus, callback)
|
||||
))
|
||||
}
|
||||
|
||||
export async function signListingRow(
|
||||
listing: ListingRow,
|
||||
signer: JsonRpcSigner,
|
||||
provider: Web3Provider,
|
||||
getLooksRareNonce: () => number,
|
||||
setLooksRareNonce: (nonce: number) => void,
|
||||
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
|
||||
) {
|
||||
const looksRareNonce = getLooksRareNonce()
|
||||
const callback = () => {
|
||||
return signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
|
||||
}
|
||||
setListingStatusAndCallback(listing, ListingStatus.SIGNING, callback)
|
||||
const { asset, marketplace } = listing
|
||||
const res = await signListing(marketplace, asset, signer, provider, looksRareNonce, (newStatus: ListingStatus) =>
|
||||
setListingStatusAndCallback(listing, newStatus, callback)
|
||||
)
|
||||
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
|
||||
}
|
||||
|
||||
export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
|
||||
const total = sellAssets.reduce((total, asset: WalletAsset) => {
|
||||
if (asset.newListings?.length) {
|
||||
const maxListing = asset.newListings.reduce((a, b) => ((a.price ?? 0) > (b.price ?? 0) ? a : b))
|
||||
// LooksRare is a unique case where creator royalties are a flat 0.5% or 50 basis points
|
||||
const maxFee =
|
||||
maxListing.marketplace.fee +
|
||||
(maxListing.marketplace.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset?.basisPoints ?? 0) / 100
|
||||
return total + (maxListing.price ?? 0) - (maxListing.price ?? 0) * (maxFee / 100)
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
return total ? Math.round(total * 10000 + Number.EPSILON) / 10000 : 0
|
||||
}
|
||||
|
||||
const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => {
|
||||
const newCollectionsToApprove: CollectionRow[] = []
|
||||
|
||||
const newListings: ListingRow[] = []
|
||||
sellAssets.forEach((asset) => {
|
||||
asset.marketplaces?.forEach((marketplace: ListingMarket) => {
|
||||
const newListing = {
|
||||
images: [asset.smallImageUrl, marketplace.icon],
|
||||
name: asset.name || `#${asset.tokenId}`,
|
||||
status: ListingStatus.DEFINED,
|
||||
asset,
|
||||
marketplace,
|
||||
price: asset.newListings?.find((listing) => listing.marketplace.name === marketplace.name)?.price,
|
||||
}
|
||||
newListings.push(newListing)
|
||||
if (
|
||||
!newCollectionsToApprove.some(
|
||||
(collectionRow: CollectionRow) =>
|
||||
collectionRow.collectionAddress === asset.asset_contract.address &&
|
||||
collectionRow.marketplace.name === marketplace.name
|
||||
)
|
||||
) {
|
||||
const newCollectionRow = {
|
||||
images: [asset.asset_contract.image_url, marketplace.icon],
|
||||
name: asset.asset_contract.name,
|
||||
status: ListingStatus.DEFINED,
|
||||
collectionAddress: asset.asset_contract.address,
|
||||
isVerified: asset.collectionIsVerified,
|
||||
marketplace,
|
||||
}
|
||||
newCollectionsToApprove.push(newCollectionRow)
|
||||
}
|
||||
})
|
||||
})
|
||||
return [newCollectionsToApprove, newListings]
|
||||
}
|
||||
|
||||
export const verifyStatus = (status: ListingStatus) => {
|
||||
return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED
|
||||
}
|
||||
|
||||
export function useSubscribeListingState() {
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const { setListings, setCollectionsRequiringApproval } = useNFTList(
|
||||
({ setListings, setCollectionsRequiringApproval }) => ({
|
||||
setListings,
|
||||
setCollectionsRequiringApproval,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
useEffect(() => {
|
||||
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
|
||||
setListings(newListings)
|
||||
setCollectionsRequiringApproval(newCollectionsToApprove)
|
||||
}, [sellAssets, setCollectionsRequiringApproval, setListings])
|
||||
}
|
||||
|
||||
export function useHandleGlobalPriceToggle(
|
||||
globalOverride: boolean,
|
||||
setListPrice: Dispatch<number | undefined>,
|
||||
setPrice: (price?: number) => void,
|
||||
listPrice?: number,
|
||||
globalPrice?: number
|
||||
) {
|
||||
useEffect(() => {
|
||||
let price: number | undefined
|
||||
if (globalOverride) {
|
||||
if (!listPrice) setListPrice(globalPrice)
|
||||
price = globalPrice
|
||||
} else {
|
||||
price = listPrice
|
||||
}
|
||||
setPrice(price)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [globalOverride])
|
||||
}
|
||||
|
||||
export function useSyncPriceWithGlobalMethod(
|
||||
asset: WalletAsset,
|
||||
setListPrice: Dispatch<number | undefined>,
|
||||
setGlobalPrice: Dispatch<number | undefined>,
|
||||
setGlobalOverride: Dispatch<boolean>,
|
||||
listPrice?: number,
|
||||
globalPrice?: number,
|
||||
globalPriceMethod?: SetPriceMethod
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) {
|
||||
setListPrice(asset?.floorPrice)
|
||||
setGlobalPrice(asset.floorPrice)
|
||||
} else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
|
||||
setListPrice(asset.lastPrice)
|
||||
setGlobalPrice(asset.lastPrice)
|
||||
} else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
|
||||
listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
|
||||
|
||||
setGlobalOverride(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [globalPriceMethod])
|
||||
}
|
||||
|
||||
export function useUpdateInputAndWarnings(
|
||||
setWarningType: Dispatch<WarningType>,
|
||||
inputRef: React.MutableRefObject<HTMLInputElement>,
|
||||
asset: WalletAsset,
|
||||
listPrice?: number
|
||||
) {
|
||||
useEffect(() => {
|
||||
setWarningType(WarningType.NONE)
|
||||
const price = listPrice ?? 0
|
||||
inputRef.current.value = `${price}`
|
||||
if (price < (asset?.floorPrice ?? 0) && price > 0) setWarningType(WarningType.BELOW_FLOOR)
|
||||
else if (asset.floor_sell_order_price && price >= asset.floor_sell_order_price)
|
||||
setWarningType(WarningType.ALREADY_LISTED)
|
||||
}, [asset?.floorPrice, asset.floor_sell_order_price, inputRef, listPrice, setWarningType])
|
||||
}
|
||||
|
||||
export const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
|
||||
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
|
||||
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
|
||||
|
||||
return baseFee * 0.01
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export const ProfilePage = () => {
|
||||
borderRadius="12"
|
||||
paddingX="16"
|
||||
paddingY="12"
|
||||
background="backgroundModule"
|
||||
background="backgroundSurface"
|
||||
borderStyle="solid"
|
||||
borderColor="backgroundOutline"
|
||||
borderWidth="1px"
|
||||
|
||||
@@ -18,27 +18,10 @@ export const subheadSmall = sprinkles({ fontWeight: 'medium', fontSize: '14', li
|
||||
export const body = sprinkles({ fontWeight: 'normal', fontSize: '16', lineHeight: '24' })
|
||||
export const bodySmall = sprinkles({ fontWeight: 'normal', fontSize: '14', lineHeight: '20' })
|
||||
export const caption = sprinkles({ fontWeight: 'normal', fontSize: '12', lineHeight: '16' })
|
||||
export const badge = sprinkles({ fontWeight: 'semibold', fontSize: '10', lineHeight: '12' })
|
||||
|
||||
export const buttonTextMedium = sprinkles({ fontWeight: 'semibold', fontSize: '16', lineHeight: '20' })
|
||||
export const buttonTextSmall = sprinkles({ fontWeight: 'semibold', fontSize: '14', lineHeight: '16' })
|
||||
|
||||
export const commonButtonStyles = style([
|
||||
sprinkles({
|
||||
borderRadius: '12',
|
||||
transition: '250',
|
||||
}),
|
||||
{
|
||||
border: 'none',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
':disabled': {
|
||||
cursor: 'auto',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const magicalGradient = style({
|
||||
selectors: {
|
||||
'&::before': {
|
||||
|
||||
@@ -4,12 +4,10 @@ import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface NFTListState {
|
||||
looksRareNonce: number
|
||||
listingStatus: ListingStatus
|
||||
listings: ListingRow[]
|
||||
collectionsRequiringApproval: CollectionRow[]
|
||||
setLooksRareNonce: (nonce: number) => void
|
||||
getLooksRareNonce: () => number
|
||||
setListingStatus: (status: ListingStatus) => void
|
||||
setListings: (listings: ListingRow[]) => void
|
||||
setCollectionsRequiringApproval: (collections: CollectionRow[]) => void
|
||||
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
|
||||
@@ -23,7 +21,6 @@ interface NFTListState {
|
||||
export const useNFTList = create<NFTListState>()(
|
||||
devtools((set, get) => ({
|
||||
looksRareNonce: 0,
|
||||
listingStatus: ListingStatus.DEFINED,
|
||||
listings: [],
|
||||
collectionsRequiringApproval: [],
|
||||
setLooksRareNonce: (nonce) =>
|
||||
@@ -33,10 +30,6 @@ export const useNFTList = create<NFTListState>()(
|
||||
getLooksRareNonce: () => {
|
||||
return get().looksRareNonce
|
||||
},
|
||||
setListingStatus: (status) =>
|
||||
set(() => {
|
||||
return { listingStatus: status }
|
||||
}),
|
||||
setListings: (listings) =>
|
||||
set(() => {
|
||||
const updatedListings = listings.map((listing) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import create from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { ListingMarket, ListingWarning, WalletAsset } from '../types'
|
||||
import { ListingMarket, WalletAsset } from '../types'
|
||||
|
||||
interface SellAssetState {
|
||||
sellAssets: WalletAsset[]
|
||||
@@ -16,10 +16,6 @@ interface SellAssetState {
|
||||
removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void
|
||||
toggleShowResolveIssues: () => void
|
||||
setIssues: (issues: number) => void
|
||||
// TODO: After merging v2, see if this marketplace logic can be removed
|
||||
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
|
||||
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
|
||||
removeAllMarketplaceWarnings: () => void
|
||||
}
|
||||
|
||||
export const useSellAsset = create<SellAssetState>()(
|
||||
@@ -118,47 +114,6 @@ export const useSellAsset = create<SellAssetState>()(
|
||||
return { sellAssets: assetsCopy }
|
||||
})
|
||||
},
|
||||
addMarketplaceWarning: (asset, warning) => {
|
||||
set(({ sellAssets }) => {
|
||||
const assetsCopy = [...sellAssets]
|
||||
asset.listingWarnings?.push(warning)
|
||||
const index = sellAssets.findIndex(
|
||||
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
|
||||
)
|
||||
assetsCopy[index] = asset
|
||||
return { sellAssets: assetsCopy }
|
||||
})
|
||||
},
|
||||
removeMarketplaceWarning: (asset, warning, setGlobalOverride?) => {
|
||||
set(({ sellAssets }) => {
|
||||
const assetsCopy = [...sellAssets]
|
||||
if (asset.listingWarnings === undefined || asset.newListings === undefined) return { sellAssets: assetsCopy }
|
||||
const warningIndex =
|
||||
asset.listingWarnings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
|
||||
asset.listingWarnings?.splice(warningIndex, 1)
|
||||
if (warning?.message?.includes('LISTING BELOW FLOOR')) {
|
||||
if (setGlobalOverride) {
|
||||
asset.newListings?.forEach((listing) => (listing.overrideFloorPrice = true))
|
||||
} else {
|
||||
const listingIndex =
|
||||
asset.newListings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
|
||||
asset.newListings[listingIndex].overrideFloorPrice = true
|
||||
}
|
||||
}
|
||||
const index = sellAssets.findIndex(
|
||||
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
|
||||
)
|
||||
assetsCopy[index] = asset
|
||||
return { sellAssets: assetsCopy }
|
||||
})
|
||||
},
|
||||
removeAllMarketplaceWarnings: () => {
|
||||
set(({ sellAssets }) => {
|
||||
const assetsCopy = [...sellAssets]
|
||||
assetsCopy.map((asset) => (asset.listingWarnings = []))
|
||||
return { sellAssets: assetsCopy }
|
||||
})
|
||||
},
|
||||
toggleShowResolveIssues: () => {
|
||||
set(({ showResolveIssues }) => {
|
||||
return { showResolveIssues: !showResolveIssues }
|
||||
|
||||
@@ -15,6 +15,7 @@ import { CollectionPageSkeleton } from 'nft/components/collection/CollectionPage
|
||||
import { BagCloseIcon } from 'nft/components/icons'
|
||||
import { useBag, useCollectionFilters, useFiltersExpanded, useIsMobile } from 'nft/hooks'
|
||||
import * as styles from 'nft/pages/collection/index.css'
|
||||
import { blocklistedCollections } from 'nft/utils'
|
||||
import { Suspense, useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { animated, easings, useSpring } from 'react-spring'
|
||||
@@ -85,7 +86,7 @@ const FiltersContainer = styled.div<{ isMobile: boolean; isFiltersExpanded: bool
|
||||
width: ${({ isMobile }) => (isMobile ? '100%' : '0px')};
|
||||
height: ${({ isMobile, isFiltersExpanded }) => (isMobile && isFiltersExpanded ? '100%' : undefined)};
|
||||
background: ${({ theme, isMobile }) => (isMobile ? theme.backgroundBackdrop : undefined)};
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
z-index: ${Z_INDEX.modalBackdrop - 3};
|
||||
overflow-y: ${({ isMobile }) => (isMobile ? 'scroll' : undefined)};
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
@@ -188,7 +189,7 @@ const Collection = () => {
|
||||
width: CollectionContainerWidthChange.to((x) => `calc(100% - ${x as number}px)`),
|
||||
}}
|
||||
>
|
||||
{contractAddress ? (
|
||||
{contractAddress && !blocklistedCollections.includes(contractAddress) ? (
|
||||
<>
|
||||
<BannerWrapper>
|
||||
<Banner
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfacePageName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
|
||||
import { ListPage } from 'nft/components/profile/list/ListPage'
|
||||
import { ProfilePage } from 'nft/components/profile/view/ProfilePage'
|
||||
import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton'
|
||||
import { useBag, useNFTList, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { ListingStatus, ProfilePageStateType } from 'nft/types'
|
||||
import { useBag, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { Suspense, useEffect, useRef } from 'react'
|
||||
import { useToggleWalletModal } from 'state/application/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -26,17 +26,17 @@ const ProfilePageWrapper = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const LoadedAccountPage = styled.div<{ cartExpanded: boolean; isOnV2ListPage: boolean }>`
|
||||
const LoadedAccountPage = styled.div<{ cartExpanded: boolean; isListingNfts: boolean }>`
|
||||
width: calc(
|
||||
100% -
|
||||
${({ cartExpanded, isOnV2ListPage }) =>
|
||||
isOnV2ListPage ? LIST_PAGE_MARGIN * 2 : cartExpanded ? XXXL_BAG_WIDTH : 0}px
|
||||
${({ cartExpanded, isListingNfts }) =>
|
||||
isListingNfts ? LIST_PAGE_MARGIN * 2 : cartExpanded ? XXXL_BAG_WIDTH : 0}px
|
||||
);
|
||||
margin: 0px ${({ isOnV2ListPage }) => (isOnV2ListPage ? LIST_PAGE_MARGIN : 0)}px;
|
||||
margin: 0px ${({ isListingNfts }) => (isListingNfts ? LIST_PAGE_MARGIN : 0)}px;
|
||||
|
||||
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
|
||||
width: calc(100% - ${({ isOnV2ListPage }) => (isOnV2ListPage ? LIST_PAGE_MARGIN_MOBILE * 2 : 0)}px);
|
||||
margin: 0px ${({ isOnV2ListPage }) => (isOnV2ListPage ? LIST_PAGE_MARGIN_MOBILE : 0)}px;
|
||||
width: calc(100% - ${({ isListingNfts }) => (isListingNfts ? LIST_PAGE_MARGIN_MOBILE * 2 : 0)}px);
|
||||
margin: 0px ${({ isListingNfts }) => (isListingNfts ? LIST_PAGE_MARGIN_MOBILE : 0)}px;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -62,15 +62,8 @@ const ConnectWalletButton = styled(ButtonPrimary)`
|
||||
const ProfileContent = () => {
|
||||
const sellPageState = useProfilePageState((state) => state.state)
|
||||
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
|
||||
const removeAllMarketplaceWarnings = useSellAsset((state) => state.removeAllMarketplaceWarnings)
|
||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const setListingStatus = useNFTList((state) => state.setListingStatus)
|
||||
|
||||
useEffect(() => {
|
||||
removeAllMarketplaceWarnings()
|
||||
setListingStatus(ListingStatus.DEFINED)
|
||||
}, [removeAllMarketplaceWarnings, sellPageState, setListingStatus])
|
||||
|
||||
const { account } = useWeb3React()
|
||||
const accountRef = useRef(account)
|
||||
@@ -85,25 +78,23 @@ const ProfileContent = () => {
|
||||
}
|
||||
}, [account, resetSellAssets, setSellPageState, clearCollectionFilters])
|
||||
const cartExpanded = useBag((state) => state.bagExpanded)
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const isListingNfts = sellPageState === ProfilePageStateType.LISTING
|
||||
const isOnV2ListPage = isNftListV2 && isListingNfts
|
||||
|
||||
return (
|
||||
<Trace page={InterfacePageName.NFT_PROFILE_PAGE} shouldLogImpression>
|
||||
<ProfilePageWrapper>
|
||||
{account ? (
|
||||
<LoadedAccountPage cartExpanded={cartExpanded} isOnV2ListPage={isOnV2ListPage}>
|
||||
<LoadedAccountPage cartExpanded={cartExpanded} isListingNfts={isListingNfts}>
|
||||
{!isListingNfts ? <ProfilePage /> : <ListPage />}
|
||||
</LoadedAccountPage>
|
||||
) : (
|
||||
<Center>
|
||||
<ThemedText.HeadlineMedium lineHeight="36px" color="textSecondary" fontWeight="600" marginBottom="24px">
|
||||
No items to display
|
||||
<Trans>No items to display</Trans>
|
||||
</ThemedText.HeadlineMedium>
|
||||
<ConnectWalletButton onClick={toggleWalletModal}>
|
||||
<ThemedText.SubHeader color="white" lineHeight="20px">
|
||||
Connect Wallet
|
||||
<Trans>Connect Wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ConnectWalletButton>
|
||||
</Center>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { blocklistedCollections } from 'nft/utils'
|
||||
|
||||
import { GenieCollection } from '../../types'
|
||||
|
||||
@@ -45,15 +46,17 @@ export const fetchSearchCollections = async (addressOrName: string, recursive =
|
||||
if (isName) {
|
||||
const data = await r.json()
|
||||
const formattedData = data?.data
|
||||
? data.data.map((collection: { stats: Record<string, unknown>; floorPrice: string }) => {
|
||||
return {
|
||||
...collection,
|
||||
stats: {
|
||||
...collection.stats,
|
||||
floor_price: collection.floorPrice,
|
||||
},
|
||||
}
|
||||
})
|
||||
? data.data
|
||||
.filter((collection: { address: string }) => !blocklistedCollections.includes(collection.address))
|
||||
.map((collection: { stats: Record<string, unknown>; floorPrice: string }) => {
|
||||
return {
|
||||
...collection,
|
||||
stats: {
|
||||
...collection.stats,
|
||||
floor_price: collection.floorPrice,
|
||||
},
|
||||
}
|
||||
})
|
||||
: []
|
||||
return formattedData.slice(0, MAX_SEARCH_RESULTS)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { blocklistedCollections } from 'nft/utils'
|
||||
|
||||
import { TimePeriod, TrendingCollection } from '../../types'
|
||||
|
||||
const NFT_API_URL = process.env.REACT_APP_TEMP_API_URL
|
||||
@@ -18,5 +20,5 @@ export const fetchTrendingCollections = async (payload: {
|
||||
|
||||
const data = await r.json()
|
||||
|
||||
return data ?? []
|
||||
return data.filter((collection: { address: string }) => !blocklistedCollections.includes(collection.address)) ?? []
|
||||
}
|
||||
|
||||
@@ -6,11 +6,6 @@ export interface ListingMarket {
|
||||
name: string
|
||||
fee: number
|
||||
icon: string
|
||||
royalty?: number
|
||||
}
|
||||
export interface ListingWarning {
|
||||
marketplace: ListingMarket
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface SellOrder {
|
||||
@@ -72,7 +67,6 @@ export interface WalletAsset {
|
||||
marketAgnosticPrice?: number
|
||||
newListings?: Listing[]
|
||||
marketplaces?: ListingMarket[]
|
||||
listingWarnings?: ListingWarning[]
|
||||
}
|
||||
|
||||
export interface WalletCollection {
|
||||
|
||||
@@ -22,3 +22,8 @@ export const isInSameSudoSwapPool = (assetA: GenieAsset, assetB: GenieAsset): bo
|
||||
export const isInSameMarketplaceCollection = (assetA: GenieAsset, assetB: GenieAsset): boolean => {
|
||||
return assetA.address === assetB.address && assetA.marketplace === assetB.marketplace
|
||||
}
|
||||
|
||||
export const blocklistedCollections = [
|
||||
'0xd5eeac01b0d1d929d6cffaaf78020af137277293',
|
||||
'0x85c08fffa9510f87019efdcf986301873cbb10d6',
|
||||
]
|
||||
|
||||
@@ -58,11 +58,13 @@ const Profile = lazy(() => import('nft/pages/profile/profile'))
|
||||
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
||||
|
||||
// Placeholder API key. Actual API key used in the proxy server
|
||||
const ANALYTICS_DUMMY_KEY = '00000000000000000000000000000000'
|
||||
const ANALYTICS_PROXY_URL = process.env.REACT_APP_AMPLITUDE_PROXY_URL
|
||||
const AMPLITUDE_DUMMY_KEY = '00000000000000000000000000000000'
|
||||
const AMPLITUDE_PROXY_URL = process.env.REACT_APP_AMPLITUDE_PROXY_URL
|
||||
const STATSIG_DUMMY_KEY = 'client-0000000000000000000000000000000000000000000'
|
||||
const STATSIG_PROXY_URL = process.env.REACT_APP_STATSIG_PROXY_URL
|
||||
const COMMIT_HASH = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||
initializeAnalytics(ANALYTICS_DUMMY_KEY, OriginApplication.INTERFACE, {
|
||||
proxyUrl: ANALYTICS_PROXY_URL,
|
||||
initializeAnalytics(AMPLITUDE_DUMMY_KEY, OriginApplication.INTERFACE, {
|
||||
proxyUrl: AMPLITUDE_PROXY_URL,
|
||||
defaultEventName: SharedEventName.PAGE_VIEWED,
|
||||
commitHash: COMMIT_HASH,
|
||||
isProductionEnv: isProductionEnv(),
|
||||
@@ -210,10 +212,11 @@ export default function App() {
|
||||
<StatsigProvider
|
||||
user={statsigUser}
|
||||
// TODO: replace with proxy and cycle key
|
||||
sdkKey={process.env.REACT_APP_STATSIG_API_KEY ?? ''}
|
||||
sdkKey={STATSIG_DUMMY_KEY}
|
||||
waitForInitialization={false}
|
||||
options={{
|
||||
environment: { tier: getEnvName() },
|
||||
api: STATSIG_PROXY_URL,
|
||||
}}
|
||||
>
|
||||
<HeaderWrapper transparent={isHeaderTransparent}>
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function MigrateV2() {
|
||||
<BodyWrapper style={{ padding: 24 }}>
|
||||
<AutoColumn gap="16px">
|
||||
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
|
||||
<BackArrow to="/pool/v2" />
|
||||
<BackArrow to="/pool" />
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Migrate V2 Liquidity</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DEFAULT_LIST_OF_LISTS } from './../constants/lists'
|
||||
|
||||
const DEFAULT_LIST_PRIORITIES = DEFAULT_LIST_OF_LISTS.reduce<{ [listUrl: string]: number }>((memo, listUrl, index) => {
|
||||
memo[listUrl] = index + 1
|
||||
return memo
|
||||
}, {})
|
||||
const DEFAULT_LIST_PRIORITIES = DEFAULT_LIST_OF_LISTS.reduce(
|
||||
(acc, listUrl, index) => ({
|
||||
...acc,
|
||||
[listUrl]: index + 1,
|
||||
}),
|
||||
{}
|
||||
) as Record<string, number>
|
||||
|
||||
// use ordering of default list of lists to assign priority
|
||||
export default function sortByListPriority(urlA: string, urlB: string) {
|
||||
if (DEFAULT_LIST_PRIORITIES[urlA] && DEFAULT_LIST_PRIORITIES[urlB]) {
|
||||
return DEFAULT_LIST_PRIORITIES[urlA] - DEFAULT_LIST_PRIORITIES[urlB]
|
||||
}
|
||||
return 0
|
||||
const A = DEFAULT_LIST_PRIORITIES[urlA]
|
||||
const B = DEFAULT_LIST_PRIORITIES[urlB]
|
||||
if (!A) return 0
|
||||
if (!B) return 0
|
||||
return A - B
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
|
||||
export function getNativeTokenDBAddress(chain: Chain): string | undefined {
|
||||
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||
if (pageChainId === undefined) {
|
||||
return undefined
|
||||
}
|
||||
switch (chain) {
|
||||
case Chain.Celo:
|
||||
case Chain.Polygon:
|
||||
|
||||
Binary file not shown.
408
yarn.lock
408
yarn.lock
@@ -1156,12 +1156,12 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@>=7.17.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.19.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78"
|
||||
integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==
|
||||
"@babel/runtime@>=7.17.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.15.4", "@babel/template@^7.16.7", "@babel/template@^7.18.10", "@babel/template@^7.3.3":
|
||||
version "7.18.10"
|
||||
@@ -1210,25 +1210,26 @@
|
||||
exec-sh "^0.3.2"
|
||||
minimist "^1.2.0"
|
||||
|
||||
"@coinbase/wallet-sdk@^3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.3.0.tgz#e77383bce1388e5367dc35f8309702e73be47503"
|
||||
integrity sha512-Prmxs5eYRxe5i+kDSsny97oPG4Pa5PhLmNDx8f7UQrvlPowGy5Tg0gHOqCie6ck2shVMdW8sKJ+RCLIRZ9kIjA==
|
||||
"@coinbase/wallet-sdk@^3.6.4":
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.6.4.tgz#00b4325c501ec5cdd07ac1b365ab226cb3df3a22"
|
||||
integrity sha512-2ecCT0/pmaMNVyMF7J1ZLFTfLnpnrHNQOGyIcbMBIepeqlE3jndjU023OdwwVLrLXyvfyelJ8K1iwAOvyEZxUw==
|
||||
dependencies:
|
||||
"@metamask/safe-event-emitter" "2.0.0"
|
||||
"@solana/web3.js" "^1.70.1"
|
||||
bind-decorator "^1.0.11"
|
||||
bn.js "^5.1.1"
|
||||
buffer "^6.0.3"
|
||||
clsx "^1.1.0"
|
||||
eth-block-tracker "4.4.3"
|
||||
eth-json-rpc-filters "4.2.2"
|
||||
eth-json-rpc-filters "5.1.0"
|
||||
eth-rpc-errors "4.0.2"
|
||||
js-sha256 "0.9.0"
|
||||
json-rpc-engine "6.1.0"
|
||||
keccak "^3.0.1"
|
||||
preact "^10.5.9"
|
||||
qs "^6.10.3"
|
||||
rxjs "^6.6.3"
|
||||
sha.js "^2.4.11"
|
||||
stream-browserify "^3.0.0"
|
||||
util "^0.12.4"
|
||||
|
||||
@@ -3251,6 +3252,21 @@
|
||||
resolved "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz"
|
||||
integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==
|
||||
|
||||
"@noble/ed25519@^1.7.0":
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123"
|
||||
integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==
|
||||
|
||||
"@noble/hashes@^1.1.2":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
|
||||
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
|
||||
|
||||
"@noble/secp256k1@^1.6.3":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
|
||||
integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@@ -3680,6 +3696,35 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@solana/buffer-layout@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15"
|
||||
integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==
|
||||
dependencies:
|
||||
buffer "~6.0.3"
|
||||
|
||||
"@solana/web3.js@^1.70.1":
|
||||
version "1.73.3"
|
||||
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.3.tgz#60e6bd68f6f364d4be360b1e0a03a0a68468a029"
|
||||
integrity sha512-vHRMo589XEIpoujpE2sZZ1aMZvfA1ImKfNxobzEFyMb+H5j6mRRUXfdgWD0qJ0sm11e5BcBC7HPeRXJB+7f3Lg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@noble/ed25519" "^1.7.0"
|
||||
"@noble/hashes" "^1.1.2"
|
||||
"@noble/secp256k1" "^1.6.3"
|
||||
"@solana/buffer-layout" "^4.0.0"
|
||||
agentkeepalive "^4.2.1"
|
||||
bigint-buffer "^1.1.5"
|
||||
bn.js "^5.0.0"
|
||||
borsh "^0.7.0"
|
||||
bs58 "^4.0.1"
|
||||
buffer "6.0.1"
|
||||
fast-stable-stringify "^1.0.0"
|
||||
jayson "^3.4.4"
|
||||
node-fetch "^2.6.7"
|
||||
rpc-websockets "^7.5.1"
|
||||
superstruct "^0.14.2"
|
||||
|
||||
"@styled-system/background@^5.1.2":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz"
|
||||
@@ -4025,13 +4070,6 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/bn.js@^4.11.3":
|
||||
version "4.11.6"
|
||||
resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz"
|
||||
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/bn.js@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
|
||||
@@ -4039,6 +4077,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/connect@^3.4.33":
|
||||
version "3.4.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
|
||||
integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/d3-array@^2":
|
||||
version "2.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.12.3.tgz#8d16d51fb04ad5a5a8ebe14eb8263a579f1efdd1"
|
||||
@@ -4444,6 +4489,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
|
||||
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
|
||||
|
||||
"@types/node@^12.12.54":
|
||||
version "12.20.55"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
|
||||
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
|
||||
|
||||
"@types/node@^13.13.5":
|
||||
version "13.13.52"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz"
|
||||
@@ -4708,6 +4758,13 @@
|
||||
anymatch "^3.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/ws@^7.4.4":
|
||||
version "7.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
|
||||
integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ws@^8.0.0":
|
||||
version "8.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
|
||||
@@ -4931,15 +4988,15 @@
|
||||
"@typescript-eslint/types" "5.47.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@uniswap/analytics-events@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.3.0.tgz#e4d6b29633c09872be3b9b760b1a192b96368887"
|
||||
integrity sha512-oShunkYEfa45RQAtl2aQfF91gfX4QirLa/fR+FyL5jfl+Ei4AZ1ihtyVjJ1VLOJlObX1p08JjlpA0yxqDwPYHw==
|
||||
"@uniswap/analytics-events@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.4.0.tgz#910f727bf4c72f5d1890f17daec4687dc44ee6c0"
|
||||
integrity sha512-oZl2KRCSTAO8C3sxEFX6BEdGujdPs8tk/Zyx7UwOZOUDcim3mxx6rsdRvnwsAgkZAJjV9I1HSUR7JEeJmQkRmQ==
|
||||
|
||||
"@uniswap/analytics@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics/-/analytics-1.3.0.tgz#42de08949a7d529ebba647d76801f143792c132a"
|
||||
integrity sha512-cwx3HDxcqehr5uUnnAJ20lak9jA68e+l8ww/s4XxoJzedkdHz0TWXc4+ZZ2iukKEun4oU/d3clrU6u3Cu6xDpg==
|
||||
"@uniswap/analytics@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics/-/analytics-1.3.1.tgz#086a681fc483c08e1b3d896cce287bc162ad0c1d"
|
||||
integrity sha512-wB8J+e9oeu+VSx3tgfA304g7MKf6zuAesFeEGKdD1pI2LNOoMQHczbnf8I8lPjPYnXxLvdbGVKRQZXWaI+jTGw==
|
||||
dependencies:
|
||||
"@amplitude/analytics-browser" "^1.5.8"
|
||||
react "^18.2.0"
|
||||
@@ -5194,10 +5251,10 @@
|
||||
"@uniswap/v3-core" "1.0.0"
|
||||
"@uniswap/v3-periphery" "^1.0.1"
|
||||
|
||||
"@uniswap/widgets@^2.29.3":
|
||||
version "2.29.3"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.29.3.tgz#1b2f0d4f7f4db16d6d12f40204bcee6a67e46c1c"
|
||||
integrity sha512-gsyiNxeqcj5Wn5IsaAKIz9M1v9lkmYKGxCrJHWADw8UyuTrPnL7qXg6GkBRR5kYkd+eeK6lK28qc1BEzl7dvmQ==
|
||||
"@uniswap/widgets@^2.40.0":
|
||||
version "2.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.40.0.tgz#98539f31cc21a8e70ced86da65db642f3380db14"
|
||||
integrity sha512-55I6r8a3XfWAEFfU6eAj4BUbaYRBPyrbfzaedAy0+9SAntnxxT7wyh3DMC9MoMLEwd4PPLemWyLfVQTVIoYn0g==
|
||||
dependencies:
|
||||
"@babel/runtime" ">=7.17.0"
|
||||
"@fontsource/ibm-plex-mono" "^4.5.1"
|
||||
@@ -6047,6 +6104,14 @@
|
||||
resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz"
|
||||
integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==
|
||||
|
||||
JSONStream@^1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||
integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
|
||||
dependencies:
|
||||
jsonparse "^1.2.0"
|
||||
through ">=2.2.7 <3"
|
||||
|
||||
abab@^2.0.3, abab@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||
@@ -6135,6 +6200,15 @@ agent-base@6:
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
agentkeepalive@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717"
|
||||
integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==
|
||||
dependencies:
|
||||
debug "^4.1.0"
|
||||
depd "^1.1.2"
|
||||
humanize-ms "^1.2.1"
|
||||
|
||||
aggregate-error@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
|
||||
@@ -6940,6 +7014,13 @@ big.js@^5.2.2:
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||
|
||||
bigint-buffer@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442"
|
||||
integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
|
||||
bignumber.js@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885"
|
||||
@@ -6965,9 +7046,9 @@ bind-decorator@^1.0.11:
|
||||
resolved "https://registry.npmjs.org/bind-decorator/-/bind-decorator-1.0.11.tgz"
|
||||
integrity sha1-5BvAah9l3ZzsR2yRxdrzl4SIJS8=
|
||||
|
||||
bindings@^1.5.0:
|
||||
bindings@^1.3.0, bindings@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
||||
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
@@ -7011,7 +7092,7 @@ bn.js@4.11.8:
|
||||
resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz"
|
||||
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9:
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
@@ -7054,6 +7135,15 @@ boolbase@^1.0.0, boolbase@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||
|
||||
borsh@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a"
|
||||
integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==
|
||||
dependencies:
|
||||
bn.js "^5.2.0"
|
||||
bs58 "^4.0.0"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
|
||||
boxen@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz"
|
||||
@@ -7203,10 +7293,10 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.19.1, browserslist@^4
|
||||
node-releases "^2.0.6"
|
||||
update-browserslist-db "^1.0.9"
|
||||
|
||||
bs58@^4.0.0:
|
||||
bs58@^4.0.0, bs58@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz"
|
||||
integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
|
||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
|
||||
integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==
|
||||
dependencies:
|
||||
base-x "^3.0.2"
|
||||
|
||||
@@ -7226,11 +7316,6 @@ bser@2.1.1:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
btoa@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz"
|
||||
integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz"
|
||||
@@ -7284,6 +7369,14 @@ buffer-xor@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
|
||||
|
||||
buffer@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2"
|
||||
integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
buffer@^4.3.0:
|
||||
version "4.9.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
|
||||
@@ -7301,7 +7394,7 @@ buffer@^5.2.0, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
buffer@^6.0.3, buffer@~6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
|
||||
@@ -7309,10 +7402,10 @@ buffer@^6.0.3:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
bufferutil@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433"
|
||||
integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==
|
||||
bufferutil@^4.0.1, bufferutil@^4.0.6:
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad"
|
||||
integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
@@ -7842,7 +7935,7 @@ cliui@^8.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clone@2.x, clone@^2.1.1:
|
||||
clone@2.x:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
@@ -7986,7 +8079,7 @@ commander@7:
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^2.20.0:
|
||||
commander@^2.20.0, commander@^2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
@@ -9184,15 +9277,20 @@ delaunator@5:
|
||||
dependencies:
|
||||
robust-predicates "^3.0.0"
|
||||
|
||||
delay@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
|
||||
integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||
|
||||
depd@~1.1.2:
|
||||
depd@^1.1.2, depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
||||
|
||||
dependency-graph@^0.11.0:
|
||||
version "0.11.0"
|
||||
@@ -9690,6 +9788,18 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3:
|
||||
es5-ext "^0.10.35"
|
||||
es6-symbol "^3.1.1"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||
integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
es6-symbol@^3.1.1, es6-symbol@~3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
|
||||
@@ -10080,35 +10190,17 @@ eth-block-tracker@4.4.3:
|
||||
pify "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-json-rpc-filters@4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz#eb35e1dfe9357ace8a8908e7daee80b2cd60a10d"
|
||||
integrity sha512-DGtqpLU7bBg63wPMWg1sCpkKCf57dJ+hj/k3zF26anXMzkmtSBDExL8IhUu7LUd34f0Zsce3PYNO2vV2GaTzaw==
|
||||
eth-json-rpc-filters@5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-5.1.0.tgz#f0c2aeaec2a45e2dc6ca1b9843d8e85447821427"
|
||||
integrity sha512-fos+9xmoa1A2Ytsc9eYof17r81BjdJOUcGcgZn4K/tKdCCTb+a8ytEtwlu1op5qsXFDlgGmstTELFrDEc89qEQ==
|
||||
dependencies:
|
||||
"@metamask/safe-event-emitter" "^2.0.0"
|
||||
async-mutex "^0.2.6"
|
||||
eth-json-rpc-middleware "^6.0.0"
|
||||
eth-query "^2.1.2"
|
||||
json-rpc-engine "^6.1.0"
|
||||
pify "^5.0.0"
|
||||
|
||||
eth-json-rpc-middleware@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-6.0.0.tgz"
|
||||
integrity sha512-qqBfLU2Uq1Ou15Wox1s+NX05S9OcAEL4JZ04VZox2NS0U+RtCMjSxzXhLFWekdShUPZ+P8ax3zCO2xcPrp6XJQ==
|
||||
dependencies:
|
||||
btoa "^1.2.1"
|
||||
clone "^2.1.1"
|
||||
eth-query "^2.1.2"
|
||||
eth-rpc-errors "^3.0.0"
|
||||
eth-sig-util "^1.4.2"
|
||||
ethereumjs-util "^5.1.2"
|
||||
json-rpc-engine "^5.3.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
node-fetch "^2.6.1"
|
||||
pify "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
eth-query@^2.1.0, eth-query@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz"
|
||||
@@ -10124,21 +10216,6 @@ eth-rpc-errors@4.0.2, eth-rpc-errors@^4.0.2:
|
||||
dependencies:
|
||||
fast-safe-stringify "^2.0.6"
|
||||
|
||||
eth-rpc-errors@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-3.0.0.tgz"
|
||||
integrity sha512-iPPNHPrLwUlR9xCSYm7HHQjWBasor3+KZfRvwEWxMz3ca0yqnlBeJrnyphkGIXZ4J7AMAaOLmwy4AWhnxOiLxg==
|
||||
dependencies:
|
||||
fast-safe-stringify "^2.0.6"
|
||||
|
||||
eth-sig-util@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz"
|
||||
integrity sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=
|
||||
dependencies:
|
||||
ethereumjs-abi "git+https://github.com/ethereumjs/ethereumjs-abi.git"
|
||||
ethereumjs-util "^5.1.1"
|
||||
|
||||
ethereum-bloom-filters@^1.0.6:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a"
|
||||
@@ -10167,39 +10244,6 @@ ethereum-cryptography@^0.1.3:
|
||||
secp256k1 "^4.0.1"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
"ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git":
|
||||
version "0.6.8"
|
||||
resolved "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0"
|
||||
dependencies:
|
||||
bn.js "^4.11.8"
|
||||
ethereumjs-util "^6.0.0"
|
||||
|
||||
ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz"
|
||||
integrity sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==
|
||||
dependencies:
|
||||
bn.js "^4.11.0"
|
||||
create-hash "^1.1.2"
|
||||
elliptic "^6.5.2"
|
||||
ethereum-cryptography "^0.1.3"
|
||||
ethjs-util "^0.1.3"
|
||||
rlp "^2.0.0"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
ethereumjs-util@^6.0.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz"
|
||||
integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.3"
|
||||
bn.js "^4.11.0"
|
||||
create-hash "^1.1.2"
|
||||
elliptic "^6.5.2"
|
||||
ethereum-cryptography "^0.1.3"
|
||||
ethjs-util "0.1.6"
|
||||
rlp "^2.2.3"
|
||||
|
||||
ethereumjs-util@^7.1.0:
|
||||
version "7.1.5"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181"
|
||||
@@ -10255,14 +10299,6 @@ ethjs-unit@0.1.6:
|
||||
bn.js "4.11.6"
|
||||
number-to-bn "1.7.0"
|
||||
|
||||
ethjs-util@0.1.6, ethjs-util@^0.1.3:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz"
|
||||
integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==
|
||||
dependencies:
|
||||
is-hex-prefixed "1.0.0"
|
||||
strip-hex-prefix "1.0.0"
|
||||
|
||||
eval@0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc"
|
||||
@@ -10523,6 +10559,11 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
|
||||
|
||||
eyes@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz"
|
||||
@@ -10564,6 +10605,11 @@ fast-safe-stringify@^2.0.6:
|
||||
resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz"
|
||||
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
|
||||
|
||||
fast-stable-stringify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313"
|
||||
integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==
|
||||
|
||||
fast-url-parser@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz"
|
||||
@@ -11676,6 +11722,13 @@ human-signals@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||
|
||||
humanize-ms@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
|
||||
integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
|
||||
@@ -12483,6 +12536,11 @@ isomorphic-ws@5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
|
||||
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==
|
||||
|
||||
isomorphic-ws@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
|
||||
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
@@ -12539,6 +12597,25 @@ javascript-stringify@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
|
||||
integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==
|
||||
|
||||
jayson@^3.4.4:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.7.0.tgz#b735b12d06d348639ae8230d7a1e2916cb078f25"
|
||||
integrity sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==
|
||||
dependencies:
|
||||
"@types/connect" "^3.4.33"
|
||||
"@types/node" "^12.12.54"
|
||||
"@types/ws" "^7.4.4"
|
||||
JSONStream "^1.3.5"
|
||||
commander "^2.20.3"
|
||||
delay "^5.0.0"
|
||||
es6-promisify "^5.0.0"
|
||||
eyes "^0.1.8"
|
||||
isomorphic-ws "^4.0.1"
|
||||
json-stringify-safe "^5.0.1"
|
||||
lodash "^4.17.20"
|
||||
uuid "^8.3.2"
|
||||
ws "^7.4.5"
|
||||
|
||||
jest-changed-files@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
|
||||
@@ -13019,7 +13096,7 @@ js-base64@^3.7.2:
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.2.tgz#816d11d81a8aff241603d19ce5761e13e41d7745"
|
||||
integrity sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==
|
||||
|
||||
js-sha256@0.9.0, js-sha256@^0.9.0:
|
||||
js-sha256@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
|
||||
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
|
||||
@@ -13120,14 +13197,6 @@ json-rpc-engine@6.1.0, json-rpc-engine@^6.1.0:
|
||||
"@metamask/safe-event-emitter" "^2.0.0"
|
||||
eth-rpc-errors "^4.0.2"
|
||||
|
||||
json-rpc-engine@^5.3.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz"
|
||||
integrity sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g==
|
||||
dependencies:
|
||||
eth-rpc-errors "^3.0.0"
|
||||
safe-event-emitter "^1.0.1"
|
||||
|
||||
json-rpc-random-id@^1.0.0, json-rpc-random-id@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz"
|
||||
@@ -13160,10 +13229,10 @@ json-stable-stringify@^1.0.1:
|
||||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
|
||||
|
||||
json-to-pretty-yaml@^1.2.2:
|
||||
version "1.2.2"
|
||||
@@ -13211,6 +13280,11 @@ jsonify@~0.0.0:
|
||||
resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
|
||||
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
|
||||
|
||||
jsonparse@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||
|
||||
jsonwebtoken@^8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
@@ -16733,10 +16807,10 @@ regenerator-runtime@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
||||
regenerator-transform@^0.14.2:
|
||||
version "0.14.5"
|
||||
@@ -17106,7 +17180,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4:
|
||||
rlp@^2.2.4:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf"
|
||||
integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==
|
||||
@@ -17153,6 +17227,19 @@ rollup@^1.31.1:
|
||||
"@types/node" "*"
|
||||
acorn "^7.1.0"
|
||||
|
||||
rpc-websockets@^7.5.1:
|
||||
version "7.5.1"
|
||||
resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401"
|
||||
integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.2"
|
||||
eventemitter3 "^4.0.7"
|
||||
uuid "^8.3.2"
|
||||
ws "^8.5.0"
|
||||
optionalDependencies:
|
||||
bufferutil "^4.0.1"
|
||||
utf-8-validate "^5.0.2"
|
||||
|
||||
rsvp@^4.8.4:
|
||||
version "4.8.5"
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
@@ -17518,7 +17605,7 @@ setprototypeof@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
|
||||
sha.js@^2.4.0, sha.js@^2.4.8:
|
||||
sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
@@ -18213,6 +18300,11 @@ stylehacks@^4.0.0:
|
||||
postcss "^7.0.0"
|
||||
postcss-selector-parser "^3.0.0"
|
||||
|
||||
superstruct@^0.14.2:
|
||||
version "0.14.2"
|
||||
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b"
|
||||
integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
@@ -18431,6 +18523,11 @@ test-value@^2.1.0:
|
||||
array-back "^1.0.3"
|
||||
typical "^2.6.0"
|
||||
|
||||
text-encoding-utf-8@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13"
|
||||
integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==
|
||||
|
||||
text-table@0.2.0, text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||
@@ -18459,10 +18556,10 @@ through2@^2.0.0:
|
||||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
through@^2.3.6, through@^2.3.8:
|
||||
"through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
|
||||
|
||||
thunky@^1.0.2:
|
||||
version "1.1.0"
|
||||
@@ -19164,10 +19261,10 @@ use@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
|
||||
|
||||
utf-8-validate@^5.0.8:
|
||||
version "5.0.9"
|
||||
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.9.tgz#ba16a822fbeedff1a58918f2a6a6b36387493ea3"
|
||||
integrity sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==
|
||||
utf-8-validate@^5.0.2, utf-8-validate@^5.0.8:
|
||||
version "5.0.10"
|
||||
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2"
|
||||
integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
@@ -19929,11 +20026,16 @@ ws@7.5.3:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
|
||||
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
|
||||
|
||||
ws@8.11.0, ws@>=7.4.6:
|
||||
ws@8.11.0:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
||||
|
||||
ws@>=7.4.6, ws@^8.5.0:
|
||||
version "8.12.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f"
|
||||
integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==
|
||||
|
||||
ws@^6.2.1:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
|
||||
|
||||
Reference in New Issue
Block a user