diff --git a/src/components/AccountDrawer/AuthenticatedHeader.tsx b/src/components/AccountDrawer/AuthenticatedHeader.tsx
index 9d05d4a5c0d..bd1bdf6fb7d 100644
--- a/src/components/AccountDrawer/AuthenticatedHeader.tsx
+++ b/src/components/AccountDrawer/AuthenticatedHeader.tsx
@@ -30,7 +30,6 @@ import StatusIcon from '../Identicon/StatusIcon'
import { useCachedPortfolioBalancesQuery } from '../PrefetchBalancesWrapper/PrefetchBalancesWrapper'
import { useToggleAccountDrawer } from '.'
import IconButton, { IconHoverText, IconWithConfirmTextButton } from './IconButton'
-import MiniPortfolio from './MiniPortfolio'
import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow'
const AuthenticatedHeaderWrapper = styled.div`
@@ -242,7 +241,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
View and sell NFTs
)}
-
{isUnclaimed && (
Claim {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} reward
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx
deleted file mode 100644
index e4842851594..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import Column from 'components/Column'
-import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
-import { LoaderV2 } from 'components/Icons/LoadingSpinner'
-import Row from 'components/Row'
-import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
-import useENSName from 'hooks/useENSName'
-import { useCallback } from 'react'
-import styled from 'styled-components'
-import { EllipsisStyle, ThemedText } from 'theme/components'
-import { shortenAddress } from 'utils'
-import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
-
-import { PortfolioLogo } from '../PortfolioLogo'
-import PortfolioRow from '../PortfolioRow'
-import { useOpenOffchainActivityModal } from './OffchainActivityModal'
-import { useTimeSince } from './parseRemote'
-import { Activity } from './types'
-
-const ActivityRowDescriptor = styled(ThemedText.BodySmall)`
- color: ${({ theme }) => theme.neutral2};
- ${EllipsisStyle}
-`
-
-const StyledTimestamp = styled(ThemedText.BodySmall)`
- color: ${({ theme }) => theme.neutral2};
- font-variant: small;
- font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
-`
-
-function StatusIndicator({ activity: { status, timestamp } }: { activity: Activity }) {
- const timeSince = useTimeSince(timestamp)
-
- switch (status) {
- case TransactionStatus.Pending:
- return
- case TransactionStatus.Confirmed:
- return {timeSince}
- case TransactionStatus.Failed:
- return
- }
-}
-
-export function ActivityRow({ activity }: { activity: Activity }) {
- const { chainId, title, descriptor, logos, otherAccount, currencies, hash, prefixIconSrc, offchainOrderStatus } =
- activity
- const openOffchainActivityModal = useOpenOffchainActivityModal()
-
- const { ENSName } = useENSName(otherAccount)
- const onClick = useCallback(() => {
- if (offchainOrderStatus) {
- openOffchainActivityModal({ orderHash: hash, status: offchainOrderStatus })
- return
- }
-
- window.open(getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION), '_blank')
- }, [offchainOrderStatus, chainId, hash, openOffchainActivityModal])
-
- return (
-
-
-
- }
- title={
-
- {prefixIconSrc && }
- {title}
-
- }
- descriptor={
-
- {descriptor}
- {ENSName ?? shortenAddress(otherAccount)}
-
- }
- right={}
- onClick={onClick}
- />
- )
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap b/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap
deleted file mode 100644
index f5f3b5fd9fe..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap
+++ /dev/null
@@ -1,347 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`parseRemote parseRemoteActivities should parse NFT approval 1`] = `
-Object {
- "chainId": 1,
- "descriptor": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Unknown Approval",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse NFT approval for all 1`] = `
-Object {
- "chainId": 1,
- "descriptor": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Unknown Approval",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse NFT receive 1`] = `
-Object {
- "chainId": 1,
- "currencies": undefined,
- "descriptor": "1 SomeCollectionName from ",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "imageUrl",
- ],
- "nonce": 12345,
- "otherAccount": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Received",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse NFT transfer 1`] = `
-Object {
- "chainId": 1,
- "descriptor": "1 SomeCollectionName",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "imageUrl",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Minted",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse closed UniswapX order 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- ],
- "descriptor": "100 DAI for 200 WETH",
- "from": "someOfferer",
- "hash": "someHash",
- "logos": Array [
- "someUrl",
- "someUrl",
- ],
- "offchainOrderStatus": "expired",
- "prefixIconSrc": "bolt.svg",
- "status": "FAILED",
- "statusMessage": "Your swap could not be fulfilled at this time. Please try again.",
- "timestamp": 10000,
- "title": "Swap expired",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse eth wrap 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- ExtendedEther {
- "chainId": 1,
- "decimals": 18,
- "isNative": true,
- "isToken": false,
- "name": "Ether",
- "symbol": "ETH",
- },
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- ],
- "descriptor": "100 ETH for 100 WETH",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "https://token-icons.s3.amazonaws.com/eth.png",
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Wrapped",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse nft purchase 1`] = `
-Object {
- "chainId": 1,
- "descriptor": "1 SomeCollectionName",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "imageUrl",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Bought",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse receive 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- ],
- "descriptor": "100 WETH from ",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- ],
- "nonce": 12345,
- "otherAccount": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Received",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse remove liquidity 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- ],
- "descriptor": "100 WETH and 100 DAI",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- "logoUrl",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Removed Liquidity",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse send 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- ],
- "descriptor": "100 DAI to ",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- ],
- "nonce": 12345,
- "otherAccount": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Sent",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse swap 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- ],
- "descriptor": "100 DAI for 100 WETH",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Swapped",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse swap order 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- Token {
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "Wrapped Ether",
- "symbol": "WETH",
- },
- ],
- "descriptor": "100 DAI for 100 WETH",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- ],
- "nonce": 12345,
- "prefixIconSrc": "bolt.svg",
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Swapped",
-}
-`;
-
-exports[`parseRemote parseRemoteActivities should parse token approval 1`] = `
-Object {
- "chainId": 1,
- "currencies": Array [
- Token {
- "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
- "chainId": 1,
- "decimals": 18,
- "isNative": false,
- "isToken": true,
- "name": "DAI",
- "symbol": "DAI",
- },
- ],
- "descriptor": "DAI",
- "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3",
- "hash": "someHash",
- "logos": Array [
- "logoUrl",
- ],
- "nonce": 12345,
- "status": "CONFIRMED",
- "timestamp": 10000,
- "title": "Approved",
-}
-`;
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts
deleted file mode 100644
index edcc22e16fb..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts
+++ /dev/null
@@ -1,506 +0,0 @@
-import { ChainId, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, WETH9 } from '@uniswap/sdk-core'
-import { DAI } from 'constants/tokens'
-import {
- AssetActivityPartsFragment,
- Chain,
- Currency,
- NftStandard,
- SwapOrderStatus,
- TokenStandard,
- TransactionDirection,
- TransactionStatus,
- TransactionType,
-} from 'graphql/data/__generated__/types-and-hooks'
-
-const MockOrderTimestamp = 10000
-const MockRecipientAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
-const MockSenderAddress = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
-
-const mockAssetActivityPartsFragment = {
- __typename: 'AssetActivity',
- id: 'activityId',
- timestamp: MockOrderTimestamp,
- chain: Chain.Ethereum,
- details: {
- __typename: 'SwapOrderDetails',
- id: 'detailsId',
- offerer: 'offererId',
- hash: 'someHash',
- inputTokenQuantity: '100',
- outputTokenQuantity: '200',
- orderStatus: SwapOrderStatus.Open,
- inputToken: {
- __typename: 'Token',
- id: 'tokenId',
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- },
- outputToken: {
- __typename: 'Token',
- id: 'tokenId',
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- },
- },
-}
-
-const mockSwapOrderDetailsPartsFragment = {
- __typename: 'SwapOrderDetails',
- id: 'someId',
- offerer: 'someOfferer',
- hash: 'someHash',
- inputTokenQuantity: '100',
- outputTokenQuantity: '200',
- orderStatus: SwapOrderStatus.Open,
- inputToken: {
- __typename: 'Token',
- id: DAI.address,
- name: 'DAI',
- symbol: DAI.symbol,
- address: DAI.address,
- decimals: 18,
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- project: {
- __typename: 'TokenProject',
- id: 'projectId',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'imageId',
- url: 'someUrl',
- },
- },
- },
- outputToken: {
- __typename: 'Token',
- id: WETH9[1].address,
- name: 'Wrapped Ether',
- symbol: 'WETH',
- address: WETH9[1].address,
- decimals: 18,
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- project: {
- __typename: 'TokenProject',
- id: 'projectId',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'imageId',
- url: 'someUrl',
- },
- },
- },
-}
-
-const mockNftApprovalPartsFragment = {
- __typename: 'NftApproval',
- id: 'approvalId',
- nftStandard: NftStandard.Erc721, // Replace with actual enum value
- approvedAddress: '0xApprovedAddress',
- asset: {
- __typename: 'NftAsset',
- id: 'assetId',
- name: 'SomeNftName',
- tokenId: 'tokenId123',
- nftContract: {
- __typename: 'NftContract',
- id: 'nftContractId',
- chain: Chain.Ethereum, // Replace with actual enum value
- address: '0xContractAddress',
- },
- image: {
- __typename: 'Image',
- id: 'imageId',
- url: 'imageUrl',
- },
- collection: {
- __typename: 'NftCollection',
- id: 'collectionId',
- name: 'SomeCollectionName',
- },
- },
-}
-
-const mockNftApproveForAllPartsFragment = {
- __typename: 'NftApproveForAll',
- id: 'approveForAllId',
- nftStandard: NftStandard.Erc721, // Replace with actual enum value
- operatorAddress: '0xOperatorAddress',
- approved: true,
- asset: {
- __typename: 'NftAsset',
- id: 'assetId',
- name: 'SomeNftName',
- tokenId: 'tokenId123',
- nftContract: {
- __typename: 'NftContract',
- id: 'nftContractId',
- chain: Chain.Ethereum, // Replace with actual enum value
- address: '0xContractAddress',
- },
- image: {
- __typename: 'Image',
- id: 'imageId',
- url: 'imageUrl',
- },
- collection: {
- __typename: 'NftCollection',
- id: 'collectionId',
- name: 'SomeCollectionName',
- },
- },
-}
-
-const mockNftTransferPartsFragment = {
- __typename: 'NftTransfer',
- id: 'transferId',
- nftStandard: NftStandard.Erc721,
- sender: MockSenderAddress,
- recipient: MockRecipientAddress,
- direction: TransactionDirection.Out,
- asset: {
- __typename: 'NftAsset',
- id: 'assetId',
- name: 'SomeNftName',
- tokenId: 'tokenId123',
- nftContract: {
- __typename: 'NftContract',
- id: 'nftContractId',
- chain: Chain.Ethereum,
- address: '0xContractAddress',
- },
- image: {
- __typename: 'Image',
- id: 'imageId',
- url: 'imageUrl',
- },
- collection: {
- __typename: 'NftCollection',
- id: 'collectionId',
- name: 'SomeCollectionName',
- },
- },
-}
-
-const mockTokenTransferOutPartsFragment = {
- __typename: 'TokenTransfer',
- id: 'tokenTransferId',
- tokenStandard: TokenStandard.Erc20,
- quantity: '100',
- sender: MockSenderAddress,
- recipient: MockRecipientAddress,
- direction: TransactionDirection.Out,
- asset: {
- __typename: 'Token',
- id: DAI.address,
- name: 'DAI',
- symbol: 'DAI',
- address: DAI.address,
- decimals: 18,
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- project: {
- __typename: 'TokenProject',
- id: 'projectId',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'logoId',
- url: 'logoUrl',
- },
- },
- },
- transactedValue: {
- __typename: 'Amount',
- id: 'amountId',
- currency: Currency.Usd,
- value: 100,
- },
-}
-
-const mockNativeTokenTransferOutPartsFragment = {
- __typename: 'TokenTransfer',
- id: 'tokenTransferId',
- asset: {
- __typename: 'Token',
- id: 'ETH',
- name: 'Ether',
- symbol: 'ETH',
- address: null,
- decimals: 18,
- chain: 'ETHEREUM',
- standard: null,
- project: {
- __typename: 'TokenProject',
- id: 'Ethereum',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'ETH_logo',
- url: 'https://token-icons.s3.amazonaws.com/eth.png',
- },
- },
- },
- tokenStandard: 'NATIVE',
- quantity: '0.25',
- sender: MockSenderAddress,
- recipient: MockRecipientAddress,
- direction: 'OUT',
- transactedValue: {
- __typename: 'Amount',
- id: 'ETH_amount',
- currency: 'USD',
- value: 399.0225,
- },
-}
-
-const mockWrappedEthTransferInPartsFragment = {
- __typename: 'TokenTransfer',
- id: 'tokenTransferId',
- asset: {
- __typename: 'Token',
- id: 'WETH',
- name: 'Wrapped Ether',
- symbol: 'WETH',
- address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
- decimals: 18,
- chain: 'ETHEREUM',
- standard: 'ERC20',
- project: {
- __typename: 'TokenProject',
- id: 'weth_project_id',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'weth_image',
- url: 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
- },
- },
- },
- tokenStandard: 'ERC20',
- quantity: '0.25',
- sender: MockSenderAddress,
- recipient: MockRecipientAddress,
- direction: 'IN',
- transactedValue: {
- __typename: 'Amount',
- id: 'mockWethAmountId',
- currency: 'USD',
- value: 399.1334007875,
- },
-}
-
-const mockTokenTransferInPartsFragment = {
- __typename: 'TokenTransfer',
- id: 'tokenTransferId',
- tokenStandard: TokenStandard.Erc20,
- quantity: '1',
- sender: MockSenderAddress,
- recipient: MockRecipientAddress,
- direction: TransactionDirection.In,
- asset: {
- __typename: 'Token',
- id: WETH9[1].address,
- name: 'Wrapped Ether',
- symbol: 'WETH',
- address: WETH9[1].address,
- decimals: 18,
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- project: {
- __typename: 'TokenProject',
- id: 'projectId',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'logoId',
- url: 'logoUrl',
- },
- },
- },
- transactedValue: {
- __typename: 'Amount',
- id: 'amountId',
- currency: Currency.Usd,
- value: 100,
- },
-}
-
-const mockTokenApprovalPartsFragment = {
- __typename: 'TokenApproval',
- id: 'tokenApprovalId',
- tokenStandard: TokenStandard.Erc20,
- approvedAddress: DAI.address,
- quantity: '50',
- asset: {
- __typename: 'Token',
- id: 'tokenId',
- name: 'DAI',
- symbol: 'DAI',
- address: DAI.address,
- decimals: 18,
- chain: Chain.Ethereum,
- standard: TokenStandard.Erc20,
- project: {
- __typename: 'TokenProject',
- id: 'projectId',
- isSpam: false,
- logo: {
- __typename: 'Image',
- id: 'logoId',
- url: 'logoUrl',
- },
- },
- },
-}
-
-export const MockOpenUniswapXOrder = {
- ...mockAssetActivityPartsFragment,
- details: mockSwapOrderDetailsPartsFragment,
-} as AssetActivityPartsFragment
-
-export const MockClosedUniswapXOrder = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...mockSwapOrderDetailsPartsFragment,
- orderStatus: SwapOrderStatus.Expired,
- },
-} as AssetActivityPartsFragment
-
-const commonTransactionDetailsFields = {
- __typename: 'TransactionDetails',
- from: MockSenderAddress,
- hash: 'someHash',
- id: 'transactionId',
- nonce: 12345,
- status: TransactionStatus.Confirmed,
- to: MockRecipientAddress,
-}
-
-export const MockNFTApproval = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Approve,
- assetChanges: [mockNftApprovalPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockNFTApprovalForAll = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Approve,
- assetChanges: [mockNftApproveForAllPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockNFTTransfer = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Mint,
- assetChanges: [mockNftTransferPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockTokenTransfer = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Swap,
- assetChanges: [mockTokenTransferOutPartsFragment, mockTokenTransferInPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockSwapOrder = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.SwapOrder,
- assetChanges: [mockTokenTransferOutPartsFragment, mockTokenTransferInPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockTokenApproval = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Approve,
- assetChanges: [mockTokenApprovalPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockTokenSend = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Send,
- assetChanges: [mockTokenTransferOutPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockTokenReceive = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Receive,
- assetChanges: [mockTokenTransferInPartsFragment],
- },
-} as AssetActivityPartsFragment
-
-export const MockRemoveLiquidity = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- to: NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[ChainId.MAINNET],
- type: TransactionType.Receive,
- assetChanges: [
- mockTokenTransferInPartsFragment,
- {
- ...mockTokenTransferOutPartsFragment,
- direction: TransactionDirection.In,
- },
- ],
- },
-} as AssetActivityPartsFragment
-
-export const MockNFTReceive = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Receive,
- assetChanges: [
- {
- ...mockNftTransferPartsFragment,
- direction: TransactionDirection.In,
- },
- ],
- },
-} as AssetActivityPartsFragment
-
-export const MockNFTPurchase = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Swap,
- assetChanges: [
- mockTokenTransferOutPartsFragment,
- {
- ...mockNftTransferPartsFragment,
- direction: TransactionDirection.In,
- },
- ],
- },
-} as AssetActivityPartsFragment
-
-export const MockWrap = {
- ...mockAssetActivityPartsFragment,
- details: {
- ...commonTransactionDetailsFields,
- type: TransactionType.Lend,
- assetChanges: [mockNativeTokenTransferOutPartsFragment, mockWrappedEthTransferInPartsFragment],
- },
-} as AssetActivityPartsFragment
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts
index acb91b111e6..0d6e64842bc 100644
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts
+++ b/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts
@@ -1,95 +1,5 @@
-import { TransactionStatus, useActivityQuery } from 'graphql/data/__generated__/types-and-hooks'
-import { useEffect, useMemo } from 'react'
import { usePendingOrders } from 'state/signatures/hooks'
-import { usePendingTransactions, useTransactionCanceller } from 'state/transactions/hooks'
-import { useFormatter } from 'utils/formatNumbers'
-
-import { useLocalActivities } from './parseLocal'
-import { parseRemoteActivities } from './parseRemote'
-import { Activity, ActivityMap } from './types'
-
-/** Detects transactions from same account with the same nonce and different hash */
-function findCancelTx(localActivity: Activity, remoteMap: ActivityMap, account: string): string | undefined {
- // handles locally cached tx's that were stored before we started tracking nonces
- if (!localActivity.nonce || localActivity.status !== TransactionStatus.Pending) return undefined
-
- for (const remoteTx of Object.values(remoteMap)) {
- if (!remoteTx) continue
-
- // A pending tx is 'cancelled' when another tx with the same account & nonce but different hash makes it on chain
- if (
- remoteTx.nonce === localActivity.nonce &&
- remoteTx.from.toLowerCase() === account.toLowerCase() &&
- remoteTx.hash.toLowerCase() !== localActivity.hash.toLowerCase() &&
- remoteTx.chainId === localActivity.chainId
- ) {
- return remoteTx.hash
- }
- }
-
- return undefined
-}
-
-/** Deduplicates local and remote activities */
-function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap = {}): Array {
- const txHashes = [...new Set([...Object.keys(localMap), ...Object.keys(remoteMap)])]
-
- return txHashes.reduce((acc: Array, hash) => {
- const localActivity = (localMap?.[hash] ?? {}) as Activity
- const remoteActivity = (remoteMap?.[hash] ?? {}) as Activity
-
- if (localActivity.cancelled) {
- // Hides misleading activities caused by cross-chain nonce collisions previously being incorrectly labelled as cancelled txs in redux
- if (localActivity.chainId !== remoteActivity.chainId) {
- acc.push(remoteActivity)
- return acc
- }
- // Remote data only contains data of the cancel tx, rather than the original tx, so we prefer local data here
- acc.push(localActivity)
- } else {
- // Generally prefer remote values to local value because i.e. remote swap amounts are on-chain rather than client-estimated
- acc.push({ ...localActivity, ...remoteActivity } as Activity)
- }
-
- return acc
- }, [])
-}
-
-export function useAllActivities(account: string) {
- const { formatNumberOrString } = useFormatter()
- const { data, loading, refetch } = useActivityQuery({
- variables: { account },
- errorPolicy: 'all',
- fetchPolicy: 'cache-first',
- })
-
- const localMap = useLocalActivities(account)
- const remoteMap = useMemo(
- () => parseRemoteActivities(formatNumberOrString, data?.portfolios?.[0].assetActivities),
- [data?.portfolios, formatNumberOrString]
- )
- const updateCancelledTx = useTransactionCanceller()
-
- /* Updates locally stored pendings tx's when remote data contains a conflicting cancellation tx */
- useEffect(() => {
- if (!remoteMap) return
-
- Object.values(localMap).forEach((localActivity) => {
- if (!localActivity) return
-
- const cancelHash = findCancelTx(localActivity, remoteMap, account)
-
- if (cancelHash) updateCancelledTx(localActivity.hash, localActivity.chainId, cancelHash)
- })
- }, [account, localMap, remoteMap, updateCancelledTx])
-
- const combinedActivities = useMemo(
- () => (remoteMap ? combineActivities(localMap, remoteMap) : undefined),
- [localMap, remoteMap]
- )
-
- return { loading, activities: combinedActivities, refetch }
-}
+import { usePendingTransactions } from 'state/transactions/hooks'
export function usePendingActivity() {
const pendingTransactions = usePendingTransactions()
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/index.tsx
deleted file mode 100644
index 0bf8d54d936..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/index.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useAccountDrawer } from 'components/AccountDrawer'
-import Column from 'components/Column'
-import { LoadingBubble } from 'components/Tokens/loading'
-import { PollingInterval } from 'graphql/data/util'
-import { atom, useAtom } from 'jotai'
-import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
-import { useEffect, useMemo } from 'react'
-import styled from 'styled-components'
-import { ThemedText } from 'theme/components'
-
-import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
-import { ActivityRow } from './ActivityRow'
-import { useAllActivities } from './hooks'
-import { createGroups } from './utils'
-
-const ActivityGroupWrapper = styled(Column)`
- margin-top: 16px;
- gap: 8px;
-`
-
-const lastFetchedAtom = atom(0)
-
-export function ActivityTab({ account }: { account: string }) {
- const [drawerOpen, toggleWalletDrawer] = useAccountDrawer()
- const [lastFetched, setLastFetched] = useAtom(lastFetchedAtom)
-
- const { activities, loading, refetch } = useAllActivities(account)
-
- // We only refetch remote activity if the user renavigates to the activity tab by changing tabs or opening the drawer
- useEffect(() => {
- const currentTime = Date.now()
- if (!lastFetched) {
- setLastFetched(currentTime)
- } else if (drawerOpen && lastFetched && currentTime - lastFetched > PollingInterval.Slow) {
- refetch()
- setLastFetched(currentTime)
- }
- }, [drawerOpen, lastFetched, refetch, setLastFetched])
-
- const activityGroups = useMemo(() => createGroups(activities), [activities])
-
- if (!activityGroups && loading) {
- return (
- <>
-
-
- >
- )
- } else if (!activityGroups || activityGroups?.length === 0) {
- return
- } else {
- return (
-
- {activityGroups.map((activityGroup) => (
-
-
- {activityGroup.title}
-
-
- {activityGroup.transactions.map((activity) => (
-
- ))}
-
-
- ))}
-
- )
- }
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts
deleted file mode 100644
index 5d5d8269d4f..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts
+++ /dev/null
@@ -1,530 +0,0 @@
-import { ChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
-import { PERMIT2_ADDRESS } from '@uniswap/universal-router-sdk'
-import { DAI as MockDAI, nativeOnChain, USDC_MAINNET as MockUSDC_MAINNET, USDT as MockUSDT } from 'constants/tokens'
-import { TransactionStatus as MockTxStatus } from 'graphql/data/__generated__/types-and-hooks'
-import { ChainTokenMap } from 'hooks/Tokens'
-import {
- ExactInputSwapTransactionInfo,
- ExactOutputSwapTransactionInfo,
- TransactionDetails,
- TransactionInfo,
- TransactionType as MockTxType,
-} from 'state/transactions/types'
-import { renderHook } from 'test-utils/render'
-import { useFormatter } from 'utils/formatNumbers'
-
-import { UniswapXOrderStatus } from '../../../../lib/hooks/orders/types'
-import { SignatureDetails, SignatureType } from '../../../../state/signatures/types'
-import { signatureToActivity, transactionToActivity, useLocalActivities } from './parseLocal'
-
-function mockSwapInfo(
- type: MockTradeType,
- inputCurrency: Token,
- inputCurrencyAmountRaw: string,
- outputCurrency: Token,
- outputCurrencyAmountRaw: string
-): ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo {
- if (type === MockTradeType.EXACT_INPUT) {
- return {
- type: MockTxType.SWAP,
- tradeType: MockTradeType.EXACT_INPUT,
- inputCurrencyId: inputCurrency.address,
- inputCurrencyAmountRaw,
- outputCurrencyId: outputCurrency.address,
- expectedOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
- minimumOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
- isUniswapXOrder: false,
- }
- } else {
- return {
- type: MockTxType.SWAP,
- tradeType: MockTradeType.EXACT_OUTPUT,
- inputCurrencyId: inputCurrency.address,
- expectedInputCurrencyAmountRaw: inputCurrencyAmountRaw,
- maximumInputCurrencyAmountRaw: inputCurrencyAmountRaw,
- outputCurrencyId: outputCurrency.address,
- outputCurrencyAmountRaw,
- isUniswapXOrder: false,
- }
- }
-}
-
-const mockAccount1 = '0x000000000000000000000000000000000000000001'
-const mockAccount2 = '0x000000000000000000000000000000000000000002'
-const mockChainId = ChainId.MAINNET
-const mockSpenderAddress = PERMIT2_ADDRESS[mockChainId]
-const mockCurrencyAmountRaw = '1000000000000000000'
-const mockCurrencyAmountRawUSDC = '1000000'
-const mockApprovalAmountRaw = '10000000'
-
-function mockHash(id: string, status: MockTxStatus = MockTxStatus.Confirmed) {
- return id + status
-}
-
-function mockCommonFields(id: string, account = mockAccount2, status: MockTxStatus) {
- const hash = mockHash(id, status)
- return {
- hash,
- from: account,
- receipt:
- status === MockTxStatus.Pending
- ? undefined
- : {
- transactionHash: hash,
- status: status === MockTxStatus.Confirmed ? 1 : 0,
- },
- addedTime: 0,
- }
-}
-
-function mockMultiStatus(info: TransactionInfo, id: string): [TransactionDetails, number][] {
- // Mocks a transaction with multiple statuses
- return [
- [
- { info, ...mockCommonFields(id, mockAccount2, MockTxStatus.Pending) } as unknown as TransactionDetails,
- mockChainId,
- ],
- [
- { info, ...mockCommonFields(id, mockAccount2, MockTxStatus.Confirmed) } as unknown as TransactionDetails,
- mockChainId,
- ],
- [
- { info, ...mockCommonFields(id, mockAccount2, MockTxStatus.Failed) } as unknown as TransactionDetails,
- mockChainId,
- ],
- ]
-}
-
-const mockTokenAddressMap: ChainTokenMap = {
- [mockChainId]: {
- [MockDAI.address]: MockDAI,
- [MockUSDC_MAINNET.address]: MockUSDC_MAINNET,
- [MockUSDT.address]: MockUSDT,
- },
-}
-
-jest.mock('../../../../hooks/Tokens', () => ({
- useAllTokensMultichain: () => mockTokenAddressMap,
-}))
-
-jest.mock('../../../../state/transactions/hooks', () => {
- return {
- useMultichainTransactions: (): [TransactionDetails, number][] => {
- return [
- [
- {
- info: mockSwapInfo(
- MockTradeType.EXACT_INPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- ...mockCommonFields('0x123', mockAccount1, MockTxStatus.Confirmed),
- } as TransactionDetails,
- mockChainId,
- ],
- ...mockMultiStatus(
- mockSwapInfo(
- MockTradeType.EXACT_OUTPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- '0xswap_exact_input'
- ),
- ...mockMultiStatus(
- mockSwapInfo(
- MockTradeType.EXACT_INPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- '0xswap_exact_output'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.APPROVAL,
- tokenAddress: MockDAI.address,
- spender: mockSpenderAddress,
- amount: mockApprovalAmountRaw,
- },
- '0xapproval'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.APPROVAL,
- tokenAddress: MockUSDT.address,
- spender: mockSpenderAddress,
- amount: '0',
- },
- '0xrevoke_approval'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.WRAP,
- unwrapped: false,
- currencyAmountRaw: mockCurrencyAmountRaw,
- chainId: mockChainId,
- },
- '0xwrap'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.WRAP,
- unwrapped: true,
- currencyAmountRaw: mockCurrencyAmountRaw,
- chainId: mockChainId,
- },
- '0xunwrap'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.ADD_LIQUIDITY_V3_POOL,
- createPool: false,
- baseCurrencyId: MockUSDC_MAINNET.address,
- quoteCurrencyId: MockDAI.address,
- feeAmount: 500,
- expectedAmountBaseRaw: mockCurrencyAmountRawUSDC,
- expectedAmountQuoteRaw: mockCurrencyAmountRaw,
- },
- '0xadd_liquidity_v3'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.REMOVE_LIQUIDITY_V3,
- baseCurrencyId: MockUSDC_MAINNET.address,
- quoteCurrencyId: MockDAI.address,
- expectedAmountBaseRaw: mockCurrencyAmountRawUSDC,
- expectedAmountQuoteRaw: mockCurrencyAmountRaw,
- },
- '0xremove_liquidity_v3'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.ADD_LIQUIDITY_V2_POOL,
- baseCurrencyId: MockUSDC_MAINNET.address,
- quoteCurrencyId: MockDAI.address,
- expectedAmountBaseRaw: mockCurrencyAmountRawUSDC,
- expectedAmountQuoteRaw: mockCurrencyAmountRaw,
- },
- '0xadd_liquidity_v2'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.COLLECT_FEES,
- currencyId0: MockUSDC_MAINNET.address,
- currencyId1: MockDAI.address,
- expectedCurrencyOwed0: mockCurrencyAmountRawUSDC,
- expectedCurrencyOwed1: mockCurrencyAmountRaw,
- },
- '0xcollect_fees'
- ),
- ...mockMultiStatus(
- {
- type: MockTxType.MIGRATE_LIQUIDITY_V3,
- baseCurrencyId: MockUSDC_MAINNET.address,
- quoteCurrencyId: MockDAI.address,
- isFork: false,
- },
- '0xmigrate_v3_liquidity'
- ),
- ]
- },
- }
-})
-
-describe('parseLocalActivity', () => {
- it('returns swap activity fields with known tokens, exact input', () => {
- const { formatNumber } = renderHook(() => useFormatter()).result.current
-
- const details = {
- info: mockSwapInfo(
- MockTradeType.EXACT_INPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- receipt: {
- transactionHash: '0x123',
- status: 1,
- },
- } as TransactionDetails
- const chainId = ChainId.MAINNET
- expect(transactionToActivity(details, chainId, mockTokenAddressMap, formatNumber)).toEqual({
- chainId: 1,
- currencies: [MockUSDC_MAINNET, MockDAI],
- descriptor: '1.00 USDC for 1.00 DAI',
- hash: undefined,
- from: undefined,
- status: 'CONFIRMED',
- timestamp: NaN,
- title: 'Swapped',
- })
- })
-
- it('returns swap activity fields with known tokens, exact output', () => {
- const { formatNumber } = renderHook(() => useFormatter()).result.current
-
- const details = {
- info: mockSwapInfo(
- MockTradeType.EXACT_OUTPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- receipt: {
- transactionHash: '0x123',
- status: 1,
- },
- } as TransactionDetails
- const chainId = ChainId.MAINNET
- expect(transactionToActivity(details, chainId, mockTokenAddressMap, formatNumber)).toMatchObject({
- chainId: 1,
- currencies: [MockUSDC_MAINNET, MockDAI],
- descriptor: '1.00 USDC for 1.00 DAI',
- status: 'CONFIRMED',
- title: 'Swapped',
- })
- })
-
- it('returns swap activity fields with unknown tokens', () => {
- const { formatNumber } = renderHook(() => useFormatter()).result.current
-
- const details = {
- info: mockSwapInfo(
- MockTradeType.EXACT_INPUT,
- MockUSDC_MAINNET,
- mockCurrencyAmountRawUSDC,
- MockDAI,
- mockCurrencyAmountRaw
- ),
- receipt: {
- transactionHash: '0x123',
- status: 1,
- },
- } as TransactionDetails
- const chainId = ChainId.MAINNET
- const tokens = {} as ChainTokenMap
- expect(transactionToActivity(details, chainId, tokens, formatNumber)).toMatchObject({
- chainId: 1,
- currencies: [undefined, undefined],
- descriptor: 'Unknown for Unknown',
- status: 'CONFIRMED',
- title: 'Swapped',
- })
- })
-
- it('only returns activity for the current account', () => {
- const account1Activites = renderHook(() => useLocalActivities(mockAccount1)).result.current
- const account2Activites = renderHook(() => useLocalActivities(mockAccount2)).result.current
-
- expect(Object.values(account1Activites)).toHaveLength(1)
- expect(Object.values(account2Activites)).toHaveLength(33)
- })
-
- it('Properly uses correct tense of activity title based on tx status', () => {
- const activities = renderHook(() => useLocalActivities(mockAccount2)).result.current
-
- expect(activities[mockHash('0xswap_exact_input', MockTxStatus.Pending)]?.title).toEqual('Swapping')
- expect(activities[mockHash('0xswap_exact_input', MockTxStatus.Confirmed)]?.title).toEqual('Swapped')
- expect(activities[mockHash('0xswap_exact_input', MockTxStatus.Failed)]?.title).toEqual('Swap failed')
- })
-
- it('Adapts Swap exact input to Activity type', () => {
- const hash = mockHash('0xswap_exact_input')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Swapped',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} for 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts Swap exact output to Activity type', () => {
- const hash = mockHash('0xswap_exact_output')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Swapped',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} for 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts Approval to Activity type', () => {
- const hash = mockHash('0xapproval')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockDAI],
- title: 'Approved',
- descriptor: MockDAI.symbol,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts Revoke Approval to Activity type', () => {
- const hash = mockHash('0xrevoke_approval')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDT],
- title: 'Revoked approval',
- descriptor: MockUSDT.symbol,
- hash,
- status: MockTxStatus.Confirmed,
- })
- })
-
- it('Adapts Wrap to Activity type', () => {
- const hash = mockHash('0xwrap')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- const native = nativeOnChain(mockChainId)
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [native, native.wrapped],
- title: 'Wrapped',
- descriptor: `1.00 ${native.symbol} for 1.00 ${native.wrapped.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts Unwrap to Activity type', () => {
- const hash = mockHash('0xunwrap')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- const native = nativeOnChain(mockChainId)
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [native.wrapped, native],
- title: 'Unwrapped',
- descriptor: `1.00 ${native.wrapped.symbol} for 1.00 ${native.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts AddLiquidityV3 to Activity type', () => {
- const hash = mockHash('0xadd_liquidity_v3')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Added liquidity',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts RemoveLiquidityV3 to Activity type', () => {
- const hash = mockHash('0xremove_liquidity_v3')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Removed liquidity',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts RemoveLiquidityV2 to Activity type', () => {
- const hash = mockHash('0xadd_liquidity_v2')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Added V2 liquidity',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts CollectFees to Activity type', () => {
- const hash = mockHash('0xcollect_fees')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Collected fees',
- descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Adapts MigrateLiquidityV3 to Activity type', () => {
- const hash = mockHash('0xmigrate_v3_liquidity')
- const activity = renderHook(() => useLocalActivities(mockAccount2)).result.current[hash]
-
- expect(activity).toMatchObject({
- chainId: mockChainId,
- currencies: [MockUSDC_MAINNET, MockDAI],
- title: 'Migrated liquidity',
- descriptor: `${MockUSDC_MAINNET.symbol} and ${MockDAI.symbol}`,
- hash,
- status: MockTxStatus.Confirmed,
- from: mockAccount2,
- })
- })
-
- it('Signature to activity - returns undefined if is on chain order', () => {
- const { formatNumber } = renderHook(() => useFormatter()).result.current
-
- expect(
- signatureToActivity(
- {
- type: SignatureType.SIGN_UNISWAPX_ORDER,
- status: UniswapXOrderStatus.FILLED,
- } as SignatureDetails,
- {},
- formatNumber
- )
- ).toBeUndefined()
-
- expect(
- signatureToActivity(
- {
- type: SignatureType.SIGN_UNISWAPX_ORDER,
- status: UniswapXOrderStatus.CANCELLED,
- } as SignatureDetails,
- {},
- formatNumber
- )
- ).toBeUndefined()
- })
-})
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts
index 9d4e3d7d149..1935f6da1d9 100644
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts
+++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts
@@ -4,11 +4,9 @@ import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import UniswapXBolt from 'assets/svg/bolt.svg'
import { nativeOnChain } from 'constants/tokens'
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
-import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens'
-import { useMemo } from 'react'
-import { isOnChainOrder, useAllSignatures } from 'state/signatures/hooks'
+import { ChainTokenMap } from 'hooks/Tokens'
+import { isOnChainOrder } from 'state/signatures/hooks'
import { SignatureDetails, SignatureType } from 'state/signatures/types'
-import { useMultichainTransactions } from 'state/transactions/hooks'
import {
AddLiquidityV2PoolTransactionInfo,
AddLiquidityV3PoolTransactionInfo,
@@ -26,7 +24,7 @@ import {
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { CancelledTransactionTitleTable, getActivityTitle, OrderTextTable } from '../constants'
-import { Activity, ActivityMap } from './types'
+import { Activity } from './types'
type FormatNumberFunctionType = ReturnType['formatNumber']
@@ -265,29 +263,3 @@ export function signatureToActivity(
return undefined
}
}
-
-export function useLocalActivities(account: string): ActivityMap {
- const allTransactions = useMultichainTransactions()
- const allSignatures = useAllSignatures()
- const tokens = useAllTokensMultichain()
- const { formatNumber } = useFormatter()
-
- return useMemo(() => {
- const activityMap: ActivityMap = {}
- for (const [transaction, chainId] of allTransactions) {
- if (transaction.from !== account) continue
-
- const activity = transactionToActivity(transaction, chainId, tokens, formatNumber)
- if (activity) activityMap[transaction.hash] = activity
- }
-
- for (const signature of Object.values(allSignatures)) {
- if (signature.offerer !== account) continue
-
- const activity = signatureToActivity(signature, tokens, formatNumber)
- if (activity) activityMap[signature.id] = activity
- }
-
- return activityMap
- }, [account, allSignatures, allTransactions, formatNumber, tokens])
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx
deleted file mode 100644
index f07d9e20358..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import { act, renderHook } from '@testing-library/react'
-import ms from 'ms'
-
-import {
- MockClosedUniswapXOrder,
- MockNFTApproval,
- MockNFTApprovalForAll,
- MockNFTPurchase,
- MockNFTReceive,
- MockNFTTransfer,
- MockOpenUniswapXOrder,
- MockRemoveLiquidity,
- MockSwapOrder,
- MockTokenApproval,
- MockTokenReceive,
- MockTokenSend,
- MockTokenTransfer,
- MockWrap,
-} from './fixtures/activity'
-import { parseRemoteActivities, useTimeSince } from './parseRemote'
-
-describe('parseRemote', () => {
- beforeEach(() => {
- jest.useFakeTimers()
- })
- describe.skip('parseRemoteActivities', () => {
- it('should not parse open UniswapX order', () => {
- const result = parseRemoteActivities(jest.fn(), [MockOpenUniswapXOrder])
- expect(result).toEqual({})
- })
- it('should parse closed UniswapX order', () => {
- const result = parseRemoteActivities(jest.fn(), [MockClosedUniswapXOrder])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse NFT approval', () => {
- const result = parseRemoteActivities(jest.fn(), [MockNFTApproval])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse NFT approval for all', () => {
- const result = parseRemoteActivities(jest.fn(), [MockNFTApprovalForAll])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse NFT transfer', () => {
- const result = parseRemoteActivities(jest.fn(), [MockNFTTransfer])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse swap', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue('100'), [MockTokenTransfer])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse nft purchase', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue('100'), [MockNFTPurchase])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse token approval', () => {
- const result = parseRemoteActivities(jest.fn(), [MockTokenApproval])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse send', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue(100), [MockTokenSend])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse receive', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue(100), [MockTokenReceive])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse NFT receive', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue(100), [MockNFTReceive])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse remove liquidity', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue(100), [MockRemoveLiquidity])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse swap order', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue('100'), [MockSwapOrder])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- it('should parse eth wrap', () => {
- const result = parseRemoteActivities(jest.fn().mockReturnValue('100'), [MockWrap])
- expect(result?.['someHash']).toMatchSnapshot()
- })
- })
-
- describe('useTimeSince', () => {
- beforeEach(() => {
- jest.useFakeTimers()
- })
-
- afterEach(() => {
- jest.useRealTimers()
- })
-
- it('should initialize with the correct time since', () => {
- const timestamp = Math.floor(Date.now() / 1000) - 60 // 60 seconds ago
- const { result } = renderHook(() => useTimeSince(timestamp))
-
- expect(result.current).toBe('1m')
- })
-
- it('should update time since every second', async () => {
- const timestamp = Math.floor(Date.now() / 1000) - 50 // 50 seconds ago
- const { result, rerender } = renderHook(() => useTimeSince(timestamp))
-
- act(() => {
- jest.advanceTimersByTime(ms('1.1s'))
- })
- rerender()
-
- expect(result.current).toBe('51s')
- })
-
- it('should stop updating after 61 seconds', () => {
- const timestamp = Math.floor(Date.now() / 1000) - 61 // 61 seconds ago
- const { result, rerender } = renderHook(() => useTimeSince(timestamp))
-
- act(() => {
- jest.advanceTimersByTime(ms('121.1s'))
- })
- rerender()
-
- // maxes out at 1m
- expect(result.current).toBe('1m')
- })
- })
-})
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx
deleted file mode 100644
index b9ce67ceaf8..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx
+++ /dev/null
@@ -1,458 +0,0 @@
-import { t } from '@lingui/macro'
-import { ChainId, Currency, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESSES } from '@uniswap/sdk-core'
-import UniswapXBolt from 'assets/svg/bolt.svg'
-import { nativeOnChain } from 'constants/tokens'
-import {
- ActivityType,
- AssetActivityPartsFragment,
- NftApprovalPartsFragment,
- NftApproveForAllPartsFragment,
- NftTransferPartsFragment,
- SwapOrderDetailsPartsFragment,
- SwapOrderStatus,
- TokenApprovalPartsFragment,
- TokenAssetPartsFragment,
- TokenTransferPartsFragment,
- TransactionDetailsPartsFragment,
-} from 'graphql/data/__generated__/types-and-hooks'
-import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
-import ms from 'ms'
-import { useEffect, useState } from 'react'
-import { isAddress } from 'utils'
-import { isSameAddress } from 'utils/addresses'
-import { NumberType, useFormatter } from 'utils/formatNumbers'
-
-import { OrderStatusTable, OrderTextTable } from '../constants'
-import { Activity } from './types'
-
-type TransactionChanges = {
- NftTransfer: NftTransferPartsFragment[]
- TokenTransfer: TokenTransferPartsFragment[]
- TokenApproval: TokenApprovalPartsFragment[]
- NftApproval: NftApprovalPartsFragment[]
- NftApproveForAll: NftApproveForAllPartsFragment[]
-}
-
-type FormatNumberOrStringFunctionType = ReturnType['formatNumberOrString']
-
-// TODO: Move common contract metadata to a backend service
-const UNI_IMG =
- 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png'
-
-const ENS_IMG =
- 'https://464911102-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/collections%2F2TjMAeHSzwlQgcOdL48E%2Ficon%2FKWP0gk2C6bdRPliWIA6o%2Fens%20transparent%20background.png?alt=media&token=bd28b063-5a75-4971-890c-97becea09076'
-
-const COMMON_CONTRACTS: { [key: string]: Partial | undefined } = {
- [UNI_ADDRESSES[ChainId.MAINNET].toLowerCase()]: {
- title: t`UNI Governance`,
- descriptor: t`Contract Interaction`,
- logos: [UNI_IMG],
- },
- // TODO(cartcrom): Add permit2-specific logo
- '0x000000000022d473030f116ddee9f6b43ac78ba3': {
- title: t`Permit2`,
- descriptor: t`Uniswap Protocol`,
- logos: [UNI_IMG],
- },
- '0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41': {
- title: t`Ethereum Name Service`,
- descriptor: t`Public Resolver`,
- logos: [ENS_IMG],
- },
- '0x58774bb8acd458a640af0b88238369a167546ef2': {
- title: t`Ethereum Name Service`,
- descriptor: t`DNS Registrar`,
- logos: [ENS_IMG],
- },
- '0x084b1c3c81545d370f3634392de611caabff8148': {
- title: t`Ethereum Name Service`,
- descriptor: t`Reverse Registrar`,
- logos: [ENS_IMG],
- },
- '0x283af0b28c62c092c9727f1ee09c02ca627eb7f5': {
- title: t`Ethereum Name Service`,
- descriptor: t`ETH Registrar Controller`,
- logos: [ENS_IMG],
- },
-}
-
-function callsPositionManagerContract(assetActivity: TransactionActivity) {
- const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
- if (!supportedChain) return false
- return isSameAddress(assetActivity.details.to, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[supportedChain])
-}
-
-// Gets counts for number of NFTs in each collection present
-function getCollectionCounts(nftTransfers: NftTransferPartsFragment[]): { [key: string]: number | undefined } {
- return nftTransfers.reduce((acc, NFTChange) => {
- const key = NFTChange.asset.collection?.name ?? NFTChange.asset.name
- if (key) {
- acc[key] = (acc?.[key] ?? 0) + 1
- }
- return acc
- }, {} as { [key: string]: number | undefined })
-}
-
-function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferPartsFragment): string | undefined {
- const supportedSentChain = supportedChainIdFromGQLChain(sent.asset.chain)
- const supportedReceivedChain = supportedChainIdFromGQLChain(received.asset.chain)
- if (!supportedSentChain || !supportedReceivedChain) {
- logSentryErrorForUnsupportedChain({
- extras: { sentAsset: sent.asset, receivedAsset: received.asset },
- errorMessage: 'Invalid activity from unsupported chain received from GQL',
- })
- return undefined
- }
- if (
- sent.tokenStandard === 'NATIVE' &&
- isSameAddress(nativeOnChain(supportedSentChain).wrapped.address, received.asset.address)
- )
- return t`Wrapped`
- else if (
- received.tokenStandard === 'NATIVE' &&
- isSameAddress(nativeOnChain(supportedReceivedChain).wrapped.address, received.asset.address)
- ) {
- return t`Unwrapped`
- } else {
- return t`Swapped`
- }
-}
-
-function getSwapDescriptor({
- tokenIn,
- inputAmount,
- tokenOut,
- outputAmount,
-}: {
- tokenIn: TokenAssetPartsFragment
- outputAmount: string
- tokenOut: TokenAssetPartsFragment
- inputAmount: string
-}) {
- return `${inputAmount} ${tokenIn.symbol} for ${outputAmount} ${tokenOut.symbol}`
-}
-
-function parseSwap(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) {
- if (changes.NftTransfer.length > 0 && changes.TokenTransfer.length === 1) {
- const collectionCounts = getCollectionCounts(changes.NftTransfer)
-
- const title = changes.NftTransfer[0].direction === 'IN' ? t`Bought` : t`Sold`
- const descriptor = Object.entries(collectionCounts)
- .map(([collectionName, count]) => `${count} ${collectionName}`)
- .join()
-
- return { title, descriptor }
- }
- // Some swaps may have more than 2 transfers, e.g. swaps with fees on tranfer
- if (changes.TokenTransfer.length >= 2) {
- const sent = changes.TokenTransfer.find((t) => t.direction === 'OUT')
- // Any leftover native token is refunded on exact_out swaps where the input token is native
- const refund = changes.TokenTransfer.find(
- (t) => t.direction === 'IN' && t.asset.id === sent?.asset.id && t.asset.standard === 'NATIVE'
- )
- const received = changes.TokenTransfer.find((t) => t.direction === 'IN' && t !== refund)
-
- if (sent && received) {
- const adjustedInput = parseFloat(sent.quantity) - parseFloat(refund?.quantity ?? '0')
- const inputAmount = formatNumberOrString({ input: adjustedInput, type: NumberType.TokenNonTx })
- const outputAmount = formatNumberOrString({ input: received.quantity, type: NumberType.TokenNonTx })
- return {
- title: getSwapTitle(sent, received),
- descriptor: getSwapDescriptor({ tokenIn: sent.asset, inputAmount, tokenOut: received.asset, outputAmount }),
- currencies: [gqlToCurrency(sent.asset), gqlToCurrency(received.asset)],
- }
- }
- }
- return { title: t`Unknown Swap` }
-}
-
-/**
- * Wrap/unwrap transactions are labelled as lend transactions on the backend.
- * This function parses the transaction changes to determine if the transaction is a wrap/unwrap transaction.
- */
-function parseLend(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) {
- const native = changes.TokenTransfer.find((t) => t.tokenStandard === 'NATIVE')?.asset
- const erc20 = changes.TokenTransfer.find((t) => t.tokenStandard === 'ERC20')?.asset
- if (native && erc20 && gqlToCurrency(native)?.wrapped.address === gqlToCurrency(erc20)?.wrapped.address) {
- return parseSwap(changes, formatNumberOrString)
- }
- return { title: t`Unknown Lend` }
-}
-
-function parseSwapOrder(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) {
- return { ...parseSwap(changes, formatNumberOrString), prefixIconSrc: UniswapXBolt }
-}
-
-function parseApprove(changes: TransactionChanges) {
- if (changes.TokenApproval.length === 1) {
- const title = parseInt(changes.TokenApproval[0].quantity) === 0 ? t`Revoked Approval` : t`Approved`
- const descriptor = `${changes.TokenApproval[0].asset.symbol}`
- const currencies = [gqlToCurrency(changes.TokenApproval[0].asset)]
- return { title, descriptor, currencies }
- }
- return { title: t`Unknown Approval` }
-}
-
-function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) {
- const poolTokenA = changes.TokenTransfer[0]
- const poolTokenB = changes.TokenTransfer[1]
-
- const tokenAQuanitity = formatNumberOrString({ input: poolTokenA.quantity, type: NumberType.TokenNonTx })
- const tokenBQuantity = formatNumberOrString({ input: poolTokenB.quantity, type: NumberType.TokenNonTx })
-
- return {
- descriptor: `${tokenAQuanitity} ${poolTokenA.asset.symbol} and ${tokenBQuantity} ${poolTokenB.asset.symbol}`,
- logos: [poolTokenA.asset.project?.logo?.url, poolTokenB.asset.project?.logo?.url],
- currencies: [gqlToCurrency(poolTokenA.asset), gqlToCurrency(poolTokenB.asset)],
- }
-}
-
-type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment }
-type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment }
-
-function parseSendReceive(
- changes: TransactionChanges,
- formatNumberOrString: FormatNumberOrStringFunctionType,
- assetActivity: TransactionActivity
-) {
- // TODO(cartcrom): remove edge cases after backend implements
- // Edge case: Receiving two token transfers in interaction w/ V3 manager === removing liquidity. These edge cases should potentially be moved to backend
- if (changes.TokenTransfer.length === 2 && callsPositionManagerContract(assetActivity)) {
- return { title: t`Removed Liquidity`, ...parseLPTransfers(changes, formatNumberOrString) }
- }
-
- let transfer: NftTransferPartsFragment | TokenTransferPartsFragment | undefined
- let assetName: string | undefined
- let amount: string | undefined
- let currencies: (Currency | undefined)[] | undefined
-
- if (changes.NftTransfer.length === 1) {
- transfer = changes.NftTransfer[0]
- assetName = transfer.asset.collection?.name
- amount = '1'
- } else if (changes.TokenTransfer.length === 1) {
- transfer = changes.TokenTransfer[0]
- assetName = transfer.asset.symbol
- amount = formatNumberOrString({ input: transfer.quantity, type: NumberType.TokenNonTx })
- currencies = [gqlToCurrency(transfer.asset)]
- }
-
- if (transfer && assetName && amount) {
- if (transfer.direction === 'IN') {
- return {
- title: t`Received`,
- descriptor: `${amount} ${assetName} ${t`from`} `,
- otherAccount: isAddress(transfer.sender) || undefined,
- currencies,
- }
- } else {
- return {
- title: t`Sent`,
- descriptor: `${amount} ${assetName} ${t`to`} `,
- otherAccount: isAddress(transfer.recipient) || undefined,
- currencies,
- }
- }
- }
- return { title: t`Unknown Send` }
-}
-
-function parseMint(
- changes: TransactionChanges,
- formatNumberOrString: FormatNumberOrStringFunctionType,
- assetActivity: TransactionActivity
-) {
- const collectionMap = getCollectionCounts(changes.NftTransfer)
- if (Object.keys(collectionMap).length === 1) {
- const collectionName = Object.keys(collectionMap)[0]
-
- // Edge case: Minting a v3 positon represents adding liquidity
- if (changes.TokenTransfer.length === 2 && callsPositionManagerContract(assetActivity)) {
- return { title: t`Added Liquidity`, ...parseLPTransfers(changes, formatNumberOrString) }
- }
- return { title: t`Minted`, descriptor: `${collectionMap[collectionName]} ${collectionName}` }
- }
- return { title: t`Unknown Mint` }
-}
-
-function parseUnknown(
- _changes: TransactionChanges,
- _formatNumberOrString: FormatNumberOrStringFunctionType,
- assetActivity: TransactionActivity
-) {
- return { title: t`Contract Interaction`, ...COMMON_CONTRACTS[assetActivity.details.to.toLowerCase()] }
-}
-
-type ActivityTypeParser = (
- changes: TransactionChanges,
- formatNumberOrString: FormatNumberOrStringFunctionType,
- assetActivity: TransactionActivity
-) => Partial
-const ActivityParserByType: { [key: string]: ActivityTypeParser | undefined } = {
- [ActivityType.Swap]: parseSwap,
- [ActivityType.Lend]: parseLend,
- [ActivityType.SwapOrder]: parseSwapOrder,
- [ActivityType.Approve]: parseApprove,
- [ActivityType.Send]: parseSendReceive,
- [ActivityType.Receive]: parseSendReceive,
- [ActivityType.Mint]: parseMint,
- [ActivityType.Unknown]: parseUnknown,
-}
-
-function getLogoSrcs(changes: TransactionChanges): Array {
- // Uses set to avoid duplicate logos (e.g. nft's w/ same image url)
- const logoSet = new Set()
- // Uses only NFT logos if they are present (will not combine nft image w/ token image)
- if (changes.NftTransfer.length > 0) {
- changes.NftTransfer.forEach((nftChange) => logoSet.add(nftChange.asset.image?.url))
- } else {
- changes.TokenTransfer.forEach((tokenChange) => logoSet.add(tokenChange.asset.project?.logo?.url))
- changes.TokenApproval.forEach((tokenChange) => logoSet.add(tokenChange.asset.project?.logo?.url))
- }
- return Array.from(logoSet)
-}
-
-function parseUniswapXOrder({ details, chain, timestamp }: OrderActivity): Activity | undefined {
- // We currently only have a polling mechanism for locally-sent pending orders, so we hide remote pending orders since they won't update upon completion
- // TODO(WEB-2487): Add polling mechanism for remote orders to allow displaying remote pending orders
- if (details.orderStatus === SwapOrderStatus.Open) return undefined
-
- const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity, orderStatus } = details
- const uniswapXOrderStatus = OrderStatusTable[orderStatus]
- const { status, statusMessage, title } = OrderTextTable[uniswapXOrderStatus]
- const descriptor = getSwapDescriptor({
- tokenIn: inputToken,
- inputAmount: inputTokenQuantity,
- tokenOut: outputToken,
- outputAmount: outputTokenQuantity,
- })
-
- const supportedChain = supportedChainIdFromGQLChain(chain)
- if (!supportedChain) {
- logSentryErrorForUnsupportedChain({
- extras: { details },
- errorMessage: 'Invalid activity from unsupported chain received from GQL',
- })
- return undefined
- }
-
- return {
- hash: details.hash,
- chainId: supportedChain,
- status,
- statusMessage,
- offchainOrderStatus: uniswapXOrderStatus,
- timestamp,
- logos: [inputToken.project?.logo?.url, outputToken.project?.logo?.url],
- currencies: [gqlToCurrency(inputToken), gqlToCurrency(outputToken)],
- title,
- descriptor,
- from: details.offerer,
- prefixIconSrc: UniswapXBolt,
- }
-}
-
-function parseRemoteActivity(
- assetActivity: AssetActivityPartsFragment,
- formatNumberOrString: FormatNumberOrStringFunctionType
-): Activity | undefined {
- try {
- if (assetActivity.details.__typename === 'SwapOrderDetails') {
- return parseUniswapXOrder(assetActivity as OrderActivity)
- }
-
- const changes = assetActivity.details.assetChanges.reduce(
- (acc: TransactionChanges, assetChange) => {
- if (assetChange.__typename === 'NftApproval') acc.NftApproval.push(assetChange)
- else if (assetChange.__typename === 'NftApproveForAll') acc.NftApproveForAll.push(assetChange)
- else if (assetChange.__typename === 'NftTransfer') acc.NftTransfer.push(assetChange)
- else if (assetChange.__typename === 'TokenTransfer') acc.TokenTransfer.push(assetChange)
- else if (assetChange.__typename === 'TokenApproval') acc.TokenApproval.push(assetChange)
-
- return acc
- },
- { NftTransfer: [], TokenTransfer: [], TokenApproval: [], NftApproval: [], NftApproveForAll: [] }
- )
- const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
- if (!supportedChain) {
- logSentryErrorForUnsupportedChain({
- extras: { assetActivity },
- errorMessage: 'Invalid activity from unsupported chain received from GQL',
- })
- return undefined
- }
-
- const defaultFields = {
- hash: assetActivity.details.hash,
- chainId: supportedChain,
- status: assetActivity.details.status,
- timestamp: assetActivity.timestamp,
- logos: getLogoSrcs(changes),
- title: assetActivity.details.type,
- descriptor: assetActivity.details.to,
- from: assetActivity.details.from,
- nonce: assetActivity.details.nonce,
- }
-
- const parsedFields = ActivityParserByType[assetActivity.details.type]?.(
- changes,
- formatNumberOrString,
- assetActivity as TransactionActivity
- )
- return { ...defaultFields, ...parsedFields }
- } catch (e) {
- console.error('Failed to parse activity', e, assetActivity)
- return undefined
- }
-}
-
-export function parseRemoteActivities(
- formatNumberOrString: FormatNumberOrStringFunctionType,
- assetActivities?: readonly AssetActivityPartsFragment[]
-) {
- return assetActivities?.reduce((acc: { [hash: string]: Activity }, assetActivity) => {
- const activity = parseRemoteActivity(assetActivity, formatNumberOrString)
- if (activity) acc[activity.hash] = activity
- return acc
- }, {})
-}
-
-const getTimeSince = (timestamp: number) => {
- const seconds = Math.floor(Date.now() - timestamp * 1000)
-
- let interval
- // TODO(cartcrom): use locale to determine date shorthands to use for non-english
- if ((interval = seconds / ms(`1y`)) > 1) return Math.floor(interval) + 'y'
- if ((interval = seconds / ms(`30d`)) > 1) return Math.floor(interval) + 'mo'
- if ((interval = seconds / ms(`1d`)) > 1) return Math.floor(interval) + 'd'
- if ((interval = seconds / ms(`1h`)) > 1) return Math.floor(interval) + 'h'
- if ((interval = seconds / ms(`1m`)) > 1) return Math.floor(interval) + 'm'
- else return Math.floor(seconds / ms(`1s`)) + 's'
-}
-
-/**
- * Keeps track of the time since a given timestamp, keeping it up to date every second when necessary
- * @param timestamp
- * @returns
- */
-export function useTimeSince(timestamp: number) {
- const [timeSince, setTimeSince] = useState(getTimeSince(timestamp))
-
- useEffect(() => {
- const refreshTime = () =>
- setTimeout(() => {
- if (Math.floor(Date.now() - timestamp * 1000) / ms(`61s`) <= 1) {
- setTimeSince(getTimeSince(timestamp))
- timeout = refreshTime()
- }
- }, ms(`1s`))
-
- let timeout = refreshTime()
-
- return () => {
- timeout && clearTimeout(timeout)
- }
- }, [timestamp])
-
- return timeSince
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/utils.test.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/utils.test.ts
deleted file mode 100644
index 0eb4dae2fea..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/utils.test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks' // Replace with the actual import if this is incorrect
-
-import { Activity } from './types'
-import { createGroups } from './utils'
-
-describe('createGroups', () => {
- it('should return undefined if activities is undefined', () => {
- expect(createGroups(undefined)).toBeUndefined()
- })
-
- it('should return an empty array if activities is empty', () => {
- expect(createGroups([])).toEqual([])
- })
-
- it('should sort and group activities based on status and time', () => {
- const mockActivities = [
- { timestamp: 1700000000, status: TransactionStatus.Pending },
- { timestamp: 1650000000, status: TransactionStatus.Confirmed },
- { timestamp: Date.now() / 1000 - 300, status: TransactionStatus.Confirmed },
- ] as Activity[]
-
- const result = createGroups(mockActivities)
-
- expect(result).toContainEqual(
- expect.objectContaining({
- title: 'Pending',
- transactions: expect.arrayContaining([
- expect.objectContaining({ timestamp: 1700000000, status: TransactionStatus.Pending }),
- ]),
- })
- )
-
- expect(result).toContainEqual(
- expect.objectContaining({
- title: 'Today',
- transactions: expect.arrayContaining([
- expect.objectContaining({ timestamp: expect.any(Number), status: TransactionStatus.Confirmed }),
- ]),
- })
- )
- })
-})
diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts
deleted file mode 100644
index 6e0ba5e5ad3..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { t } from '@lingui/macro'
-import { getYear, isSameDay, isSameMonth, isSameWeek, isSameYear } from 'date-fns'
-import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
-
-import { Activity } from './types'
-
-interface ActivityGroup {
- title: string
- transactions: Array
-}
-
-const sortActivities = (a: Activity, b: Activity) => b.timestamp - a.timestamp
-
-export const createGroups = (activities?: Array) => {
- if (!activities) return undefined
- const now = Date.now()
-
- const pending: Array = []
- const today: Array = []
- const currentWeek: Array = []
- const last30Days: Array = []
- const currentYear: Array = []
- const yearMap: { [key: string]: Array } = {}
-
- // TODO(cartcrom): create different time bucket system for activities to fall in based on design wants
- activities.forEach((activity) => {
- if (activity.status === TransactionStatus.Pending) {
- pending.push(activity)
- return
- }
- const addedTime = activity.timestamp * 1000
-
- if (isSameDay(now, addedTime)) {
- today.push(activity)
- } else if (isSameWeek(addedTime, now)) {
- currentWeek.push(activity)
- } else if (isSameMonth(addedTime, now)) {
- last30Days.push(activity)
- } else if (isSameYear(addedTime, now)) {
- currentYear.push(activity)
- } else {
- const year = getYear(addedTime)
-
- if (!yearMap[year]) {
- yearMap[year] = [activity]
- } else {
- yearMap[year].push(activity)
- }
- }
- })
- const sortedYears = Object.keys(yearMap)
- .sort((a, b) => parseInt(b) - parseInt(a))
- .map((year) => ({ title: year, transactions: yearMap[year] }))
-
- const transactionGroups: Array = [
- { title: t`Pending`, transactions: pending.sort(sortActivities) },
- { title: t`Today`, transactions: today.sort(sortActivities) },
- { title: t`This week`, transactions: currentWeek.sort(sortActivities) },
- { title: t`This month`, transactions: last30Days.sort(sortActivities) },
- { title: t`This year`, transactions: currentYear.sort(sortActivities) },
- ...sortedYears,
- ]
-
- return transactionGroups.filter((transactionInformation) => transactionInformation.transactions.length > 0)
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx b/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx
deleted file mode 100644
index daf0598fc36..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useToggleAccountDrawer } from 'components/AccountDrawer'
-import Column from 'components/Column'
-import Row from 'components/Row'
-import { Box } from 'nft/components/Box'
-import { NftCard } from 'nft/components/card'
-import { detailsHref } from 'nft/components/card/utils'
-import { VerifiedIcon } from 'nft/components/icons'
-import { WalletAsset } from 'nft/types'
-import { floorFormatter } from 'nft/utils'
-import { useNavigate } from 'react-router-dom'
-import styled from 'styled-components'
-import { ThemedText } from 'theme/components'
-
-const FloorPrice = styled(Row)`
- opacity: 0;
-
- // prevent empty whitespace from collapsing line height to maintain
- // consistent spacing below rows
- white-space: pre;
-`
-
-const NFTContainer = styled(Column)`
- gap: 8px;
- min-height: 150px;
-
- &:hover {
- ${FloorPrice} {
- opacity: 1;
- }
- }
-`
-const NFTCollectionName = styled(ThemedText.BodySmall)`
- white-space: pre;
- text-overflow: ellipsis;
- overflow: hidden;
-`
-
-export function NFT({
- asset,
- mediaShouldBePlaying,
- setCurrentTokenPlayingMedia,
-}: {
- asset: WalletAsset
- mediaShouldBePlaying: boolean
- setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
-}) {
- const toggleWalletDrawer = useToggleAccountDrawer()
- const navigate = useNavigate()
-
- const navigateToNFTDetails = () => {
- toggleWalletDrawer()
- navigate(detailsHref(asset))
- }
-
- return (
-
-
-
-
- )
-}
-
-function NFTDetails({ asset }: { asset: WalletAsset }) {
- return (
-
-
- {asset.asset_contract.name}
- {asset.collectionIsVerified && }
-
-
-
- {asset.floorPrice ? `${floorFormatter(asset.floorPrice)} ETH` : ' '}
-
-
-
- )
-}
-
-const BADGE_SIZE = '18px'
-function Verified() {
- return (
-
-
-
- )
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/NFTs/index.tsx b/src/components/AccountDrawer/MiniPortfolio/NFTs/index.tsx
deleted file mode 100644
index 4833df30ce3..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/NFTs/index.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useNftBalance } from 'graphql/data/nft/NftBalance'
-import { LoadingAssets } from 'nft/components/collection/CollectionAssetLoading'
-import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
-import { useState } from 'react'
-import InfiniteScroll from 'react-infinite-scroll-component'
-import styled from 'styled-components'
-
-import { useAccountDrawer } from '../..'
-import { DEFAULT_NFT_QUERY_AMOUNT } from '../constants'
-import { NFT } from './NFTItem'
-
-export default function NFTs({ account }: { account: string }) {
- const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer()
- const { walletAssets, loading, hasNext, loadMore } = useNftBalance(
- account,
- [],
- [],
- DEFAULT_NFT_QUERY_AMOUNT,
- undefined,
- undefined,
- undefined,
- !walletDrawerOpen
- )
-
- const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState()
-
- if (loading && !walletAssets)
- return (
-
-
-
- )
-
- if (!walletAssets || walletAssets?.length === 0) {
- return
- }
-
- return (
-
-
-
- )
- }
- dataLength={walletAssets?.length ?? 0}
- style={{ overflow: 'unset' }}
- scrollableTarget="wallet-dropdown-scroll-wrapper"
- >
-
- {walletAssets?.length
- ? walletAssets.map((asset, index) => {
- return (
-
- )
- })
- : null}
-
-
- )
-}
-
-const AssetsContainer = styled.div`
- display: grid;
- gap: 12px;
-
- // use minmax to not let grid items escape the parent container
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
- margin: 16px;
-`
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts b/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts
deleted file mode 100644
index fa7bdbf9009..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import { ChainId, Token } from '@uniswap/sdk-core'
-import { Pool, Position } from '@uniswap/v3-sdk'
-import { useAllTokensMultichain } from 'hooks/Tokens'
-import { atom, useAtom } from 'jotai'
-import { atomWithStorage } from 'jotai/utils'
-import ms from 'ms'
-import { useCallback } from 'react'
-import { deserializeToken, serializeToken } from 'state/user/hooks'
-import { SerializedToken } from 'state/user/types'
-import { PositionDetails } from 'types/position'
-import { buildCurrencyKey, currencyKey } from 'utils/currencyKey'
-
-import { getTokensAsync } from './getTokensAsync'
-import { useInterfaceMulticallContracts } from './hooks'
-
-export type PositionInfo = {
- owner: string
- chainId: ChainId
- position: Position
- pool: Pool
- details: PositionDetails
- inRange: boolean
- closed: boolean
- fees?: [number?, number?]
- prices?: [number?, number?]
-}
-
-const POSITION_CACHE_EXPIRY = ms(`1m`) // 1 minute is arbitrary here
-// Allows reusing recently fetched positions between component mounts
-type CachedPositionsEntry = { result: PositionInfo[]; stale: boolean }
-const cachedPositionsAtom = atom<{ [address: string]: CachedPositionsEntry | undefined }>({})
-type UseCachedPositionsReturnType = [CachedPositionsEntry | undefined, (positions: PositionInfo[]) => void]
-/**
- * Caches positions to allow reusing between component mounts
- * @param account address to cache positions for
- * @returns cached positions for the account, whether the cache is stale, and a function to update the positions and cache
- */
-export function useCachedPositions(account: string): UseCachedPositionsReturnType {
- const [cachedPositions, setCachedPositions] = useAtom(cachedPositionsAtom)
- const setPositionsAndStaleTimeout = useCallback(
- (positions: PositionInfo[]) => {
- setCachedPositions((cache) => ({ ...cache, [account]: { result: positions, stale: false } }))
- setTimeout(
- () =>
- setCachedPositions((cache) => {
- // sets stale to true if the positions haven't been updated since the timeout
- if (positions === cache[account]?.result) {
- return { ...cache, [account]: { result: positions, stale: true } }
- } else {
- return cache
- }
- }),
- POSITION_CACHE_EXPIRY
- )
- },
- [account, setCachedPositions]
- )
- return [cachedPositions[account], setPositionsAndStaleTimeout]
-}
-
-const poolAddressKey = (details: PositionDetails, chainId: ChainId) =>
- `${chainId}-${details.token0}-${details.token1}-${details.fee}`
-
-type PoolAddressMap = { [key: string]: string | undefined }
-const poolAddressCacheAtom = atomWithStorage('poolCache', {})
-/**
- * Caches pool addresses to prevent components from having to re-compute them
- * @returns get and set functions for the cache
- */
-export function usePoolAddressCache() {
- const [cache, updateCache] = useAtom(poolAddressCacheAtom)
- const get = useCallback(
- (details: PositionDetails, chainId: ChainId) => cache[poolAddressKey(details, chainId)],
- [cache]
- )
- const set = useCallback(
- (details: PositionDetails, chainId: ChainId, address: string) =>
- updateCache((c) => ({ ...c, [poolAddressKey(details, chainId)]: address })),
- [updateCache]
- )
- return { get, set }
-}
-
-// These values are static, so we can persist them across sessions using `WithStorage`
-const tokenCacheAtom = atomWithStorage<{ [key: string]: SerializedToken | undefined }>('cachedAsyncTokens', {})
-function useTokenCache() {
- const [cache, setCache] = useAtom(tokenCacheAtom)
- const get = useCallback(
- (chainId: number, address: string) => {
- const entry = cache[buildCurrencyKey(chainId, address)]
- return entry ? deserializeToken(entry) : undefined
- },
- [cache]
- )
- const set = useCallback(
- (token?: Token) => {
- if (token) {
- setCache((cache) => ({ ...cache, [currencyKey(token)]: serializeToken(token) }))
- }
- },
- [setCache]
- )
- return { get, set }
-}
-
-type TokenGetterFn = (addresses: string[], chainId: ChainId) => Promise<{ [key: string]: Token | undefined }>
-export function useGetCachedTokens(chains: ChainId[]): TokenGetterFn {
- const allTokens = useAllTokensMultichain()
- const multicallContracts = useInterfaceMulticallContracts(chains)
- const tokenCache = useTokenCache()
-
- // Used to fetch tokens not available in local state
- const fetchRemoteTokens: TokenGetterFn = useCallback(
- async (addresses, chainId) => {
- const fetched = await getTokensAsync(addresses, chainId, multicallContracts[chainId])
- Object.values(fetched).forEach(tokenCache.set)
- return fetched
- },
- [multicallContracts, tokenCache]
- )
-
- // Uses tokens from local state if available, otherwise fetches them
- const getTokens: TokenGetterFn = useCallback(
- async (addresses, chainId) => {
- const local: { [address: string]: Token | undefined } = {}
- const missing = new Set()
- addresses.forEach((address) => {
- const cached = tokenCache.get(chainId, address) ?? allTokens[chainId]?.[address]
- cached ? (local[address] = cached) : missing.add(address)
- })
-
- const fetched = await fetchRemoteTokens([...missing], chainId)
- return { ...local, ...fetched }
- },
- [allTokens, fetchRemoteTokens, tokenCache]
- )
-
- return getTokens
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts b/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts
deleted file mode 100644
index eae544385e7..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { ChainId, Token } from '@uniswap/sdk-core'
-import ERC20_ABI from 'abis/erc20.json'
-import { Erc20Interface } from 'abis/types/Erc20'
-import { Erc20Bytes32Interface } from 'abis/types/Erc20Bytes32'
-import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
-import { Interface } from 'ethers/lib/utils'
-import { UniswapInterfaceMulticall } from 'types/v3'
-import { isAddress } from 'utils'
-import { arrayToSlices } from 'utils/arrays'
-import { buildCurrencyKey, CurrencyKey, currencyKey } from 'utils/currencyKey'
-
-type TokenMap = { [address: string]: Token | undefined }
-export type Call = { target: string; callData: string; gasLimit: number }
-type CallResult = { success: boolean; returnData: string }
-export const DEFAULT_GAS_LIMIT = 1_000_000
-
-const Erc20 = new Interface(ERC20_ABI) as Erc20Interface
-const Erc20Bytes32 = new Interface(ERC20_ABI) as Erc20Bytes32Interface // Used for tokens that return bytes32 for name/symbol rather than string
-
-// TODO(WEB-1760): cartcrom - adapt support for multi-function multi-interface multicalls into redux-multicall to remove than this custom cache/chunking logic
-// Infura rejects calls with gas costs > 10x the current block gas limit; in such case we split the call into 2 chunks
-async function fetchChunk(multicall: UniswapInterfaceMulticall, chunk: Call[]): Promise {
- try {
- return (await multicall.callStatic.multicall(chunk)).returnData
- } catch (error) {
- if (error.code === -32603 || error.message?.indexOf('execution ran out of gas') !== -1) {
- if (chunk.length > 1) {
- const half = Math.floor(chunk.length / 2)
- return Promise.all([
- fetchChunk(multicall, chunk.slice(0, half)),
- fetchChunk(multicall, chunk.slice(half, chunk.length)),
- ]).then(([c0, c1]) => [...c0, ...c1])
- }
- }
- console.error('Failed to fetch chunk', error)
- throw error
- }
-}
-
-function tryParseToken(address: string, chainId: ChainId, data: CallResult[]) {
- try {
- const [nameData, symbolData, decimalsData, nameDataBytes32, symbolDataBytes32] = data
-
- const name = nameData.success
- ? (Erc20.decodeFunctionResult('name', nameData.returnData)[0] as string)
- : nameDataBytes32.success
- ? (Erc20Bytes32.decodeFunctionResult('name', nameDataBytes32.returnData)[0] as string)
- : undefined
- const symbol = symbolData.success
- ? (Erc20.decodeFunctionResult('symbol', symbolData.returnData)[0] as string)
- : symbolDataBytes32.success
- ? (Erc20Bytes32.decodeFunctionResult('symbol', symbolDataBytes32.returnData)[0] as string)
- : undefined
- const decimals = decimalsData.success ? parseInt(decimalsData.returnData) : DEFAULT_ERC20_DECIMALS
-
- return new Token(chainId, address, decimals, symbol, name)
- } catch (error) {
- console.error(`Failed to fetch token at address ${address} on chain ${chainId}`)
- return undefined
- }
-}
-
-function parseTokens(addresses: string[], chainId: ChainId, returnData: CallResult[]) {
- const tokenDataSlices = arrayToSlices(returnData, 5)
-
- return tokenDataSlices.reduce((acc: TokenMap, slice, index) => {
- const parsedToken = tryParseToken(addresses[index], chainId, slice)
- if (parsedToken) acc[parsedToken.address] = parsedToken
- return acc
- }, {})
-}
-
-const createCalls = (target: string, callData: string[]): Call[] =>
- callData.map((callData) => ({ target, callData, gasLimit: DEFAULT_GAS_LIMIT }))
-
-function createCallsForToken(address: string) {
- return createCalls(address, [
- Erc20.encodeFunctionData('name'),
- Erc20.encodeFunctionData('symbol'),
- Erc20.encodeFunctionData('decimals'),
- Erc20Bytes32.encodeFunctionData('name'),
- Erc20Bytes32.encodeFunctionData('symbol'),
- ])
-}
-
-// Prevents tokens from being fetched multiple times
-const TokenPromiseCache: { [key: CurrencyKey]: Promise | undefined } = {}
-
-// Returns tokens using a single RPC call to the multicall contract
-export async function getTokensAsync(
- addresses: string[],
- chainId: ChainId,
- multicall: UniswapInterfaceMulticall
-): Promise {
- if (addresses.length === 0) return {}
- const formattedAddresses: string[] = []
- const calls: Call[] = []
- const previouslyCalledTokens: Promise[] = []
-
- addresses.forEach((tokenAddress) => {
- const key = buildCurrencyKey(chainId, tokenAddress)
- const previousCall = TokenPromiseCache[key]
- if (previousCall !== undefined) {
- previouslyCalledTokens.push(previousCall)
- } else {
- const formattedAddress = isAddress(tokenAddress)
- if (!formattedAddress) return
- formattedAddresses.push(formattedAddress)
- calls.push(...createCallsForToken(formattedAddress))
- }
- })
-
- const calledTokens = fetchChunk(multicall, calls).then((returnData) => parseTokens(addresses, chainId, returnData))
-
- // Caches tokens currently being fetched for further calls to use
- formattedAddresses.forEach(
- (address) =>
- (TokenPromiseCache[buildCurrencyKey(chainId, address)] = calledTokens.then((tokenMap) => tokenMap[address]))
- )
-
- const tokenMap = await calledTokens
- // Add tokens from previous calls to the map of tokens fetched in this call
- const resolvedPreviousTokens = await Promise.all(previouslyCalledTokens)
- resolvedPreviousTokens.forEach((token) => token && (tokenMap[currencyKey(token)] = token))
-
- return tokenMap
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts b/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts
deleted file mode 100644
index 937eb1d6b31..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import {
- ChainId,
- MULTICALL_ADDRESSES,
- NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES,
- Token,
-} from '@uniswap/sdk-core'
-import type { AddressMap } from '@uniswap/smart-order-router'
-import MulticallJSON from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
-import NFTPositionManagerJSON from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
-import { useWeb3React } from '@web3-react/core'
-import { isSupportedChain } from 'constants/chains'
-import { DEPRECATED_RPC_PROVIDERS, RPC_PROVIDERS } from 'constants/providers'
-import { BaseContract } from 'ethers/lib/ethers'
-import { useFallbackProviderEnabled } from 'featureFlags/flags/fallbackProvider'
-import { ContractInput, useUniswapPricesQuery } from 'graphql/data/__generated__/types-and-hooks'
-import { toContractInput } from 'graphql/data/util'
-import useStablecoinPrice from 'hooks/useStablecoinPrice'
-import { useMemo } from 'react'
-import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'types/v3'
-import { getContract } from 'utils'
-import { CurrencyKey, currencyKey, currencyKeyFromGraphQL } from 'utils/currencyKey'
-
-import { PositionInfo } from './cache'
-
-type ContractMap = { [key: number]: T }
-
-// Constructs a chain-to-contract map, using the wallet's provider when available
-function useContractMultichain(
- addressMap: AddressMap,
- ABI: any,
- chainIds?: ChainId[]
-): ContractMap {
- const { chainId: walletChainId, provider: walletProvider } = useWeb3React()
-
- const networkProviders = useFallbackProviderEnabled() ? RPC_PROVIDERS : DEPRECATED_RPC_PROVIDERS
-
- return useMemo(() => {
- const relevantChains =
- chainIds ??
- Object.keys(addressMap)
- .map((chainId) => parseInt(chainId))
- .filter((chainId) => isSupportedChain(chainId))
-
- return relevantChains.reduce((acc: ContractMap, chainId) => {
- const provider =
- walletProvider && walletChainId === chainId
- ? walletProvider
- : isSupportedChain(chainId)
- ? networkProviders[chainId]
- : undefined
- if (provider) {
- acc[chainId] = getContract(addressMap[chainId] ?? '', ABI, provider) as T
- }
- return acc
- }, {})
- }, [ABI, addressMap, chainIds, networkProviders, walletChainId, walletProvider])
-}
-
-export function useV3ManagerContracts(chainIds: ChainId[]): ContractMap {
- return useContractMultichain(V3NFT_ADDRESSES, NFTPositionManagerJSON.abi, chainIds)
-}
-
-export function useInterfaceMulticallContracts(chainIds: ChainId[]): ContractMap {
- return useContractMultichain(MULTICALL_ADDRESSES, MulticallJSON.abi, chainIds)
-}
-
-type PriceMap = { [key: CurrencyKey]: number | undefined }
-export function usePoolPriceMap(positions: PositionInfo[] | undefined) {
- const contracts = useMemo(() => {
- if (!positions || !positions.length) return []
- // Avoids fetching duplicate tokens by placing in map
- const contractMap = positions.reduce((acc: { [key: string]: ContractInput }, { pool: { token0, token1 } }) => {
- acc[currencyKey(token0)] = toContractInput(token0)
- acc[currencyKey(token1)] = toContractInput(token1)
- return acc
- }, {})
- return Object.values(contractMap)
- }, [positions])
-
- const { data, loading } = useUniswapPricesQuery({ variables: { contracts }, skip: !contracts.length })
-
- const priceMap = useMemo(
- () =>
- data?.tokens?.reduce((acc: PriceMap, current) => {
- if (current) acc[currencyKeyFromGraphQL(current)] = current.project?.markets?.[0]?.price?.value
- return acc
- }, {}) ?? {},
- [data?.tokens]
- )
-
- return { priceMap, pricesLoading: loading && !data }
-}
-
-function useFeeValue(token: Token, fee: number | undefined, queriedPrice: number | undefined) {
- const stablecoinPrice = useStablecoinPrice(!queriedPrice ? token : undefined)
- return useMemo(() => {
- // Prefers gql price, as fetching stablecoinPrice will trigger multiple infura calls for each pool position
- const price = queriedPrice ?? (stablecoinPrice ? parseFloat(stablecoinPrice.toSignificant()) : undefined)
- const feeValue = fee && price ? fee * price : undefined
-
- return [price, feeValue]
- }, [fee, queriedPrice, stablecoinPrice])
-}
-
-export function useFeeValues(position: PositionInfo) {
- const [priceA, feeValueA] = useFeeValue(position.pool.token0, position.fees?.[0], position.prices?.[0])
- const [priceB, feeValueB] = useFeeValue(position.pool.token1, position.fees?.[1], position.prices?.[1])
-
- return { priceA, priceB, fees: (feeValueA || 0) + (feeValueB || 0) }
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx
deleted file mode 100644
index 324295df0d0..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { mocked } from 'test-utils/mocked'
-import { owner, useMultiChainPositionsReturnValue } from 'test-utils/pools/fixtures'
-import { render } from 'test-utils/render'
-
-import Pools from '.'
-import useMultiChainPositions from './useMultiChainPositions'
-
-jest.mock('./useMultiChainPositions')
-
-jest.spyOn(console, 'warn').mockImplementation()
-
-beforeEach(() => {
- mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
-})
-test('Pools should render LP positions', () => {
- const props = { account: owner }
- const { container } = render()
- expect(container).not.toBeEmptyDOMElement()
-})
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx
deleted file mode 100644
index bd4b37efecb..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import { t } from '@lingui/macro'
-import { Position } from '@uniswap/v3-sdk'
-import { useWeb3React } from '@web3-react/core'
-import { useToggleAccountDrawer } from 'components/AccountDrawer'
-import Row from 'components/Row'
-import { MouseoverTooltip } from 'components/Tooltip'
-import { useFilterPossiblyMaliciousPositions } from 'hooks/useFilterPossiblyMaliciousPositions'
-import { useSwitchChain } from 'hooks/useSwitchChain'
-import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
-import { useCallback, useMemo, useReducer } from 'react'
-import { useNavigate } from 'react-router-dom'
-import styled from 'styled-components'
-import { ThemedText } from 'theme/components'
-import { NumberType, useFormatter } from 'utils/formatNumbers'
-
-import { ExpandoRow } from '../ExpandoRow'
-import { PortfolioLogo } from '../PortfolioLogo'
-import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
-import { PositionInfo } from './cache'
-import { useFeeValues } from './hooks'
-import useMultiChainPositions from './useMultiChainPositions'
-
-/**
- * Takes an array of PositionInfo objects (format used by the Uniswap Labs gql API).
- * The hook access PositionInfo.details (format used by the NFT position contract),
- * filters the PositionDetails data for malicious content,
- * and then returns the original data in its original format.
- */
-function useFilterPossiblyMaliciousPositionInfo(positions: PositionInfo[] | undefined): PositionInfo[] {
- const tokenIdsToPositionInfo: Record = useMemo(
- () =>
- positions
- ? positions.reduce((acc, position) => ({ ...acc, [position.details.tokenId.toString()]: position }), {})
- : {},
- [positions]
- )
- const positionDetails = useMemo(() => positions?.map((position) => position.details) ?? [], [positions])
- const filteredPositionDetails = useFilterPossiblyMaliciousPositions(positionDetails)
-
- return useMemo(
- () => filteredPositionDetails.map((positionDetails) => tokenIdsToPositionInfo[positionDetails.tokenId.toString()]),
- [filteredPositionDetails, tokenIdsToPositionInfo]
- )
-}
-
-export default function Pools({ account }: { account: string }) {
- const { positions, loading } = useMultiChainPositions(account)
- const filteredPositions = useFilterPossiblyMaliciousPositionInfo(positions)
- const [showClosed, toggleShowClosed] = useReducer((showClosed) => !showClosed, false)
-
- const [openPositions, closedPositions] = useMemo(() => {
- const openPositions: PositionInfo[] = []
- const closedPositions: PositionInfo[] = []
- for (let i = 0; i < filteredPositions.length; i++) {
- const position = filteredPositions[i]
- if (position.closed) {
- closedPositions.push(position)
- } else {
- openPositions.push(position)
- }
- }
- return [openPositions, closedPositions]
- }, [filteredPositions])
-
- const toggleWalletDrawer = useToggleAccountDrawer()
-
- if (!filteredPositions || loading) {
- return
- }
-
- if (filteredPositions.length === 0) {
- return
- }
-
- return (
-
- {openPositions.map((positionInfo) => (
-
- ))}
-
- {closedPositions.map((positionInfo) => (
-
- ))}
-
-
- )
-}
-
-const ActiveDot = styled.span<{ closed: boolean; outOfRange: boolean }>`
- background-color: ${({ theme, closed, outOfRange }) =>
- closed ? theme.neutral2 : outOfRange ? theme.deprecated_accentWarning : theme.success};
- border-radius: 50%;
- height: 8px;
- width: 8px;
- margin-left: 4px;
- margin-top: 1px;
-`
-
-function calculcateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) {
- if (!price0 || !price1) return undefined
-
- const value0 = parseFloat(position.amount0.toExact()) * price0
- const value1 = parseFloat(position.amount1.toExact()) * price1
- return value0 + value1
-}
-
-function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
- const { formatNumber } = useFormatter()
-
- const { chainId, position, pool, details, inRange, closed } = positionInfo
-
- const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo)
- const liquidityValue = calculcateLiquidityValue(priceA, priceB, position)
-
- const navigate = useNavigate()
- const toggleWalletDrawer = useToggleAccountDrawer()
- const { chainId: walletChainId, connector } = useWeb3React()
- const switchChain = useSwitchChain()
- const onClick = useCallback(async () => {
- if (walletChainId !== chainId) await switchChain(connector, chainId)
- toggleWalletDrawer()
- navigate('/pool/' + details.tokenId)
- }, [walletChainId, chainId, switchChain, connector, toggleWalletDrawer, navigate, details.tokenId])
-
- return (
- }
- title={
-
-
- {pool.token0.symbol} / {pool.token1?.symbol}
-
-
- }
- descriptor={{`${pool.fee / 10000}%`}}
- right={
- <>
-
- {`${formatNumber({
- input: liquidityValue,
- type: NumberType.PortfolioBalance,
- })} (liquidity) + ${formatNumber({
- input: feeValue,
- type: NumberType.PortfolioBalance,
- })} (fees)`}
-
- }
- >
-
- {formatNumber({
- input: (liquidityValue ?? 0) + (feeValue ?? 0),
- type: NumberType.PortfolioBalance,
- })}
-
-
-
-
-
- {closed ? t`Closed` : inRange ? t`In range` : t`Out of range`}
-
-
-
- >
- }
- />
- )
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx
deleted file mode 100644
index e433848fc59..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-import { ChainId, CurrencyAmount, Token, V3_CORE_FACTORY_ADDRESSES } from '@uniswap/sdk-core'
-import IUniswapV3PoolStateJSON from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
-import { computePoolAddress, Pool, Position } from '@uniswap/v3-sdk'
-import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
-import { BigNumber } from 'ethers/lib/ethers'
-import { Interface } from 'ethers/lib/utils'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import { PositionDetails } from 'types/position'
-import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'types/v3'
-import { UniswapV3PoolInterface } from 'types/v3/UniswapV3Pool'
-import { currencyKey } from 'utils/currencyKey'
-
-import { PositionInfo, useCachedPositions, useGetCachedTokens, usePoolAddressCache } from './cache'
-import { Call, DEFAULT_GAS_LIMIT } from './getTokensAsync'
-import { useInterfaceMulticallContracts, usePoolPriceMap, useV3ManagerContracts } from './hooks'
-
-function createPositionInfo(
- owner: string,
- chainId: ChainId,
- details: PositionDetails,
- slot0: any,
- tokenA: Token,
- tokenB: Token
-): PositionInfo {
- /* Instantiates a Pool with a hardcoded 0 liqudity value since the sdk only uses this value for swap state and this avoids an RPC fetch */
- const pool = new Pool(tokenA, tokenB, details.fee, slot0.sqrtPriceX96.toString(), 0, slot0.tick)
- const position = new Position({
- pool,
- liquidity: details.liquidity.toString(),
- tickLower: details.tickLower,
- tickUpper: details.tickUpper,
- })
- const inRange = slot0.tick >= details.tickLower && slot0.tick < details.tickUpper
- const closed = details.liquidity.eq(0)
- return { owner, chainId, pool, position, details, inRange, closed }
-}
-
-type FeeAmounts = [BigNumber, BigNumber]
-
-const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1)
-
-const DEFAULT_CHAINS = [
- ChainId.MAINNET,
- ChainId.ARBITRUM_ONE,
- ChainId.OPTIMISM,
- ChainId.POLYGON,
- ChainId.CELO,
- ChainId.BNB,
- ChainId.AVALANCHE,
- ChainId.BASE,
-]
-
-type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean }
-
-/**
- * Returns all positions for a given account on multiple chains.
- *
- * This hook doesn't use the redux-multicall library to avoid having to manually fetching blocknumbers for each chain.
- *
- * @param account - account to fetch positions for
- * @param chains - chains to fetch positions from
- * @returns positions, fees
- */
-export default function useMultiChainPositions(account: string, chains = DEFAULT_CHAINS): UseMultiChainPositionsData {
- const pms = useV3ManagerContracts(chains)
- const multicalls = useInterfaceMulticallContracts(chains)
-
- const getTokens = useGetCachedTokens(chains)
- const poolAddressCache = usePoolAddressCache()
-
- const [cachedPositions, setPositions] = useCachedPositions(account)
- const positions = cachedPositions?.result
- const positionsFetching = useRef(false)
- const positionsLoading = !cachedPositions?.result && positionsFetching.current
-
- const [feeMap, setFeeMap] = useState<{ [key: string]: FeeAmounts }>({})
-
- const { priceMap, pricesLoading } = usePoolPriceMap(positions)
-
- const fetchPositionFees = useCallback(
- async (pm: NonfungiblePositionManager, positionIds: BigNumber[], chainId: number) => {
- const callData = positionIds.map((id) =>
- pm.interface.encodeFunctionData('collect', [
- { tokenId: id, recipient: account, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 },
- ])
- )
- const fees = (await pm.callStatic.multicall(callData)).reduce((acc, feeBytes, index) => {
- const key = chainId.toString() + positionIds[index]
- acc[key] = pm.interface.decodeFunctionResult('collect', feeBytes) as FeeAmounts
- return acc
- }, {} as { [key: string]: FeeAmounts })
-
- setFeeMap((prev) => ({ ...prev, ...fees }))
- },
- [account]
- )
-
- const fetchPositionIds = useCallback(
- async (pm: NonfungiblePositionManager, balance: BigNumber) => {
- const callData = Array.from({ length: balance.toNumber() }, (_, i) =>
- pm.interface.encodeFunctionData('tokenOfOwnerByIndex', [account, i])
- )
- return (await pm.callStatic.multicall(callData)).map((idByte) => BigNumber.from(idByte))
- },
- [account]
- )
-
- const fetchPositionDetails = useCallback(async (pm: NonfungiblePositionManager, positionIds: BigNumber[]) => {
- const callData = positionIds.map((id) => pm.interface.encodeFunctionData('positions', [id]))
- return (await pm.callStatic.multicall(callData)).map(
- (positionBytes, index) =>
- ({
- ...pm.interface.decodeFunctionResult('positions', positionBytes),
- tokenId: positionIds[index],
- } as unknown as PositionDetails)
- )
- }, [])
-
- // Combines PositionDetails with Pool data to build our return type
- const fetchPositionInfo = useCallback(
- async (positionDetails: PositionDetails[], chainId: ChainId, multicall: UniswapInterfaceMulticall) => {
- const poolInterface = new Interface(IUniswapV3PoolStateJSON.abi) as UniswapV3PoolInterface
- const tokens = await getTokens(
- positionDetails.flatMap((details) => [details.token0, details.token1]),
- chainId
- )
-
- const calls: Call[] = []
- const poolPairs: [Token, Token][] = []
- positionDetails.forEach((details) => {
- const tokenA = tokens[details.token0] ?? new Token(chainId, details.token0, DEFAULT_ERC20_DECIMALS)
- const tokenB = tokens[details.token1] ?? new Token(chainId, details.token1, DEFAULT_ERC20_DECIMALS)
-
- let poolAddress = poolAddressCache.get(details, chainId)
- if (!poolAddress) {
- const factoryAddress = V3_CORE_FACTORY_ADDRESSES[chainId]
- poolAddress = computePoolAddress({ factoryAddress, tokenA, tokenB, fee: details.fee })
- poolAddressCache.set(details, chainId, poolAddress)
- }
- poolPairs.push([tokenA, tokenB])
- calls.push({
- target: poolAddress,
- callData: poolInterface.encodeFunctionData('slot0'),
- gasLimit: DEFAULT_GAS_LIMIT,
- })
- }, [])
-
- return (await multicall.callStatic.multicall(calls)).returnData.reduce((acc: PositionInfo[], result, i) => {
- if (result.success) {
- const slot0 = poolInterface.decodeFunctionResult('slot0', result.returnData)
- acc.push(createPositionInfo(account, chainId, positionDetails[i], slot0, ...poolPairs[i]))
- } else {
- console.debug('slot0 fetch errored', result)
- }
- return acc
- }, [])
- },
- [account, poolAddressCache, getTokens]
- )
-
- const fetchPositionsForChain = useCallback(
- async (chainId: ChainId): Promise => {
- try {
- const pm = pms[chainId]
- const multicall = multicalls[chainId]
- const balance = await pm?.balanceOf(account)
- if (!pm || !multicall || balance.lt(1)) return []
-
- const positionIds = await fetchPositionIds(pm, balance)
- // Fetches fees in the background and stores them separetely from the results of this function
- fetchPositionFees(pm, positionIds, chainId)
-
- const postionDetails = await fetchPositionDetails(pm, positionIds)
- return fetchPositionInfo(postionDetails, chainId, multicall)
- } catch (error) {
- console.error(`Failed to fetch positions for chain ${chainId}`, error)
- return []
- }
- },
- [account, fetchPositionDetails, fetchPositionFees, fetchPositionIds, fetchPositionInfo, pms, multicalls]
- )
-
- const fetchAllPositions = useCallback(async () => {
- positionsFetching.current = true
- const positions = (await Promise.all(chains.map(fetchPositionsForChain))).flat()
- positionsFetching.current = false
- setPositions(positions)
- }, [chains, fetchPositionsForChain, setPositions])
-
- // Fetches positions when existing positions are stale and the document has focus
- useEffect(() => {
- if (positionsFetching.current || cachedPositions?.stale === false) return
- else if (document.hasFocus()) {
- fetchAllPositions()
- } else {
- // Avoids refetching positions until the user returns to Interface to avoid polling unnused rpc data
- const onFocus = () => {
- fetchAllPositions()
- window.removeEventListener('focus', onFocus)
- }
- window.addEventListener('focus', onFocus)
- return () => {
- window.removeEventListener('focus', onFocus)
- }
- }
- return
- }, [fetchAllPositions, positionsFetching, cachedPositions?.stale])
-
- const positionsWithFeesAndPrices: PositionInfo[] | undefined = useMemo(
- () =>
- positions?.map((position) => {
- const key = position.chainId.toString() + position.details.tokenId
- const fees = feeMap[key]
- ? [
- // We parse away from SDK/ethers types so fees can be multiplied by primitive number prices
- parseFloat(CurrencyAmount.fromRawAmount(position.pool.token0, feeMap[key]?.[0].toString()).toExact()),
- parseFloat(CurrencyAmount.fromRawAmount(position.pool.token1, feeMap[key]?.[1].toString()).toExact()),
- ]
- : undefined
- const prices = [priceMap[currencyKey(position.pool.token0)], priceMap[currencyKey(position.pool.token1)]]
- return { ...position, fees, prices } as PositionInfo
- }),
- [feeMap, positions, priceMap]
- )
-
- return { positions: positionsWithFeesAndPrices, loading: pricesLoading || positionsLoading }
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/PortfolioRow.tsx b/src/components/AccountDrawer/MiniPortfolio/PortfolioRow.tsx
index ffc2542d491..8063ec633ab 100644
--- a/src/components/AccountDrawer/MiniPortfolio/PortfolioRow.tsx
+++ b/src/components/AccountDrawer/MiniPortfolio/PortfolioRow.tsx
@@ -3,7 +3,7 @@ import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading'
import styled, { css, keyframes } from 'styled-components'
-export const PortfolioRowWrapper = styled(Row)<{ onClick?: any }>`
+const PortfolioRowWrapper = styled(Row)<{ onClick?: any }>`
gap: 12px;
height: 68px;
padding: 0 16px;
diff --git a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx
deleted file mode 100644
index e20296f056c..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper'
-import Row from 'components/Row'
-import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
-import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks'
-import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util'
-import { useAtomValue } from 'jotai/utils'
-import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
-import { useCallback, useMemo, useState } from 'react'
-import { useNavigate } from 'react-router-dom'
-import styled from 'styled-components'
-import { EllipsisStyle, ThemedText } from 'theme/components'
-import { NumberType, useFormatter } from 'utils/formatNumbers'
-import { splitHiddenTokens } from 'utils/splitHiddenTokens'
-
-import { useToggleAccountDrawer } from '../..'
-import { hideSmallBalancesAtom } from '../../SmallBalanceToggle'
-import { ExpandoRow } from '../ExpandoRow'
-import { PortfolioLogo } from '../PortfolioLogo'
-import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
-
-export default function Tokens({ account }: { account: string }) {
- const toggleWalletDrawer = useToggleAccountDrawer()
- const hideSmallBalances = useAtomValue(hideSmallBalancesAtom)
- const [showHiddenTokens, setShowHiddenTokens] = useState(false)
-
- const { data } = useCachedPortfolioBalancesQuery({ account })
-
- const tokenBalances = data?.portfolios?.[0].tokenBalances as TokenBalance[] | undefined
-
- const { visibleTokens, hiddenTokens } = useMemo(
- () => splitHiddenTokens(tokenBalances ?? [], { hideSmallBalances }),
- [hideSmallBalances, tokenBalances]
- )
-
- if (!data) {
- return
- }
-
- if (tokenBalances?.length === 0) {
- // TODO: consider launching moonpay here instead of just closing the drawer
- return
- }
-
- const toggleHiddenTokens = () => setShowHiddenTokens((showHiddenTokens) => !showHiddenTokens)
-
- return (
-
- {visibleTokens.map(
- (tokenBalance) =>
- tokenBalance.token &&
- )}
-
- {hiddenTokens.map(
- (tokenBalance) =>
- tokenBalance.token &&
- )}
-
-
- )
-}
-
-const TokenBalanceText = styled(ThemedText.BodySecondary)`
- ${EllipsisStyle}
-`
-const TokenNameText = styled(ThemedText.SubHeader)`
- ${EllipsisStyle}
-`
-
-type PortfolioToken = NonNullable
-
-function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) {
- const { formatPercent } = useFormatter()
- const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
-
- const navigate = useNavigate()
- const toggleWalletDrawer = useToggleAccountDrawer()
- const navigateToTokenDetails = useCallback(async () => {
- navigate(getTokenDetailsURL(token))
- toggleWalletDrawer()
- }, [navigate, token, toggleWalletDrawer])
- const { formatNumber } = useFormatter()
-
- const currency = gqlToCurrency(token)
- if (!currency) {
- logSentryErrorForUnsupportedChain({
- extras: { token },
- errorMessage: 'Token from unsupported chain received from Mini Portfolio Token Balance Query',
- })
- return null
- }
- return (
- }
- title={{token?.name}}
- descriptor={
-
- {formatNumber({
- input: quantity,
- type: NumberType.TokenNonTx,
- })}{' '}
- {token?.symbol}
-
- }
- onClick={navigateToTokenDetails}
- right={
- denominatedValue && (
- <>
-
- {formatNumber({
- input: denominatedValue?.value,
- type: NumberType.PortfolioBalance,
- })}
-
-
-
- {formatPercent(percentChange)}
-
- >
- )
- }
- />
- )
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap b/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap
deleted file mode 100644
index 6eb8b28d14d..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap
+++ /dev/null
@@ -1,164 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PortfolioLogo renders with L2 icon 1`] = `
-.c1 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- gap: 2px;
- position: relative;
- top: 0;
- left: 0;
-}
-
-.c1 img:nth-child(n) {
- width: 19px;
- height: 40px;
- object-fit: cover;
-}
-
-.c1 img:nth-child(1) {
- border-radius: 20px 0 0 20px;
- object-position: 0 0;
-}
-
-.c1 img:nth-child(2) {
- border-radius: 0 20px 20px 0;
- object-position: 100% 0;
-}
-
-.c0 {
- position: relative;
- top: 0;
- left: 0;
-}
-
-.c4 {
- height: 14px;
- width: 14px;
-}
-
-.c2 {
- width: 40px;
- height: 40px;
- border-radius: 50%;
-}
-
-.c3 {
- background-color: #222222;
- border-radius: 2px;
- height: 16px;
- left: 60%;
- position: absolute;
- top: 60%;
- outline: 2px solid #FFFFFF;
- width: 16px;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-align-items: center;
- -webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: center;
- -webkit-justify-content: center;
- -ms-flex-pack: center;
- justify-content: center;
-}
-
-
-`;
-
-exports[`PortfolioLogo renders without L2 icon 1`] = `
-.c1 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- gap: 2px;
- position: relative;
- top: 0;
- left: 0;
-}
-
-.c1 img:nth-child(n) {
- width: 19px;
- height: 40px;
- object-fit: cover;
-}
-
-.c1 img:nth-child(1) {
- border-radius: 20px 0 0 20px;
- object-position: 0 0;
-}
-
-.c1 img:nth-child(2) {
- border-radius: 0 20px 20px 0;
- object-position: 100% 0;
-}
-
-.c0 {
- position: relative;
- top: 0;
- left: 0;
-}
-
-.c2 {
- width: 40px;
- height: 40px;
- border-radius: 50%;
-}
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/components/AccountDrawer/MiniPortfolio/constants.tsx b/src/components/AccountDrawer/MiniPortfolio/constants.tsx
index 62fb9f37e13..19fc2c8bc9e 100644
--- a/src/components/AccountDrawer/MiniPortfolio/constants.tsx
+++ b/src/components/AccountDrawer/MiniPortfolio/constants.tsx
@@ -1,11 +1,8 @@
import { t } from '@lingui/macro'
-import { SwapOrderStatus, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
+import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { UniswapXOrderStatus } from 'lib/hooks/orders/types'
import { TransactionType } from 'state/transactions/types'
-// use even number because rows are in groups of 2
-export const DEFAULT_NFT_QUERY_AMOUNT = 26
-
const TransactionTitleTable: { [key in TransactionType]: { [state in TransactionStatus]: string } } = {
[TransactionType.SWAP]: {
[TransactionStatus.Pending]: t`Swapping`,
@@ -220,11 +217,3 @@ export const OrderTextTable: {
status: TransactionStatus.Failed,
},
}
-
-// Converts GQL backend orderStatus enum to the enum used by the frontend and UniswapX backend
-export const OrderStatusTable: { [key in SwapOrderStatus]: UniswapXOrderStatus } = {
- [SwapOrderStatus.Open]: UniswapXOrderStatus.OPEN,
- [SwapOrderStatus.Expired]: UniswapXOrderStatus.EXPIRED,
- [SwapOrderStatus.Error]: UniswapXOrderStatus.ERROR,
- [SwapOrderStatus.InsufficientFunds]: UniswapXOrderStatus.INSUFFICIENT_FUNDS,
-}
diff --git a/src/components/AccountDrawer/MiniPortfolio/index.tsx b/src/components/AccountDrawer/MiniPortfolio/index.tsx
deleted file mode 100644
index e07d09e3e85..00000000000
--- a/src/components/AccountDrawer/MiniPortfolio/index.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Trans } from '@lingui/macro'
-import Column from 'components/Column'
-import { LoaderV2 } from 'components/Icons/LoadingSpinner'
-import { AutoRow } from 'components/Row'
-import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
-import { useIsNftPage } from 'hooks/useIsNftPage'
-import { useEffect, useState } from 'react'
-import styled, { useTheme } from 'styled-components'
-import { BREAKPOINTS } from 'theme'
-import { ThemedText } from 'theme/components'
-
-import { ActivityTab } from './Activity'
-import { usePendingActivity } from './Activity/hooks'
-import NFTs from './NFTs'
-import Pools from './Pools'
-import { PortfolioRowWrapper } from './PortfolioRow'
-import Tokens from './Tokens'
-
-const Wrapper = styled(Column)`
- margin-top: 28px;
- display: flex;
- flex-direction: column;
- height: 100%;
- gap: 12px;
-
- @media screen and (max-width: ${BREAKPOINTS.sm}px) {
- margin-bottom: 48px;
- }
-
- ${PortfolioRowWrapper} {
- &:hover {
- background: ${({ theme }) => theme.deprecated_hoverDefault};
- }
- }
-`
-
-const Nav = styled(AutoRow)`
- gap: 20px;
-`
-
-const NavItem = styled(ThemedText.SubHeader)<{ active?: boolean }>`
- align-items: center;
- color: ${({ theme, active }) => (active ? theme.neutral1 : theme.neutral2)};
- cursor: pointer;
- display: flex;
- justify-content: space-between;
- transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} color`};
-
- &:hover {
- ${({ theme, active }) => !active && `color: ${theme.neutral2}`};
- }
-`
-
-const PageWrapper = styled.div`
- border-radius: 12px;
- margin-right: -16px;
- margin-left: -16px;
- width: calc(100% + 32px);
- flex: 1;
-`
-
-interface Page {
- title: React.ReactNode
- key: string
- component: ({ account }: { account: string }) => JSX.Element
-}
-
-const Pages: Array = [
- {
- title: Tokens,
- key: 'tokens',
- component: Tokens,
- },
- {
- title: NFTs,
- key: 'nfts',
- component: NFTs,
- },
- {
- title: Pools,
- key: 'pools',
- component: Pools,
- },
- {
- title: Activity,
- key: 'activity',
- component: ActivityTab,
- },
-]
-
-export default function MiniPortfolio({ account }: { account: string }) {
- const isNftPage = useIsNftPage()
- const theme = useTheme()
- const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : 0)
- const shouldDisableNFTRoutes = useDisableNFTRoutes()
- const [activityUnread, setActivityUnread] = useState(false)
-
- const { component: Page, key: currentKey } = Pages[currentPage]
-
- const { hasPendingActivity } = usePendingActivity()
-
- useEffect(() => {
- if (hasPendingActivity && currentKey !== 'activity') setActivityUnread(true)
- }, [currentKey, hasPendingActivity])
-
- return (
-
-
-
-
-
-
- )
-}
diff --git a/src/graphql/data/util.tsx b/src/graphql/data/util.tsx
index 65ce6f2d8ba..000ac0d0ca4 100644
--- a/src/graphql/data/util.tsx
+++ b/src/graphql/data/util.tsx
@@ -5,9 +5,8 @@ import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import ms from 'ms'
import { useEffect } from 'react'
-import { getNativeTokenDBAddress } from 'utils/nativeTokens'
-import { Chain, ContractInput, HistoryDuration, TokenStandard } from './__generated__/types-and-hooks'
+import { Chain, HistoryDuration, TokenStandard } from './__generated__/types-and-hooks'
export enum PollingInterval {
Slow = ms(`5m`),
@@ -102,10 +101,6 @@ type GqlChainsType = (typeof GQL_CHAINS)[number]
export function isGqlSupportedChain(chainId: number | undefined): chainId is GqlChainsType {
return !!chainId && GQL_CHAINS.includes(chainId)
}
-export function toContractInput(currency: Currency): ContractInput {
- const chain = chainIdToBackendName(currency.chainId)
- return { chain, address: currency.isToken ? currency.address : getNativeTokenDBAddress(chain) }
-}
export function gqlToCurrency(token: {
address?: string
diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx
index e4e6e4afd39..1f45c5eb63e 100644
--- a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx
+++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx
@@ -1,13 +1,9 @@
import userEvent from '@testing-library/user-event'
-import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
-import { mocked } from 'test-utils/mocked'
-import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
+import { validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
import { act, render, screen } from 'test-utils/render'
import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons'
-jest.mock('components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions')
-
describe('PoolDetailsStatsButton', () => {
const mockProps = {
chainId: 1,
@@ -22,10 +18,6 @@ describe('PoolDetailsStatsButton', () => {
token1: validPoolToken0,
}
- beforeEach(() => {
- mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
- })
-
it.skip('renders both buttons correctly', () => {
const { asFragment } = render()
expect(asFragment()).toMatchSnapshot()
diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx
index 497b202725d..5b69aa5faa1 100644
--- a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx
+++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx
@@ -1,7 +1,5 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
-import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache'
-import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Row from 'components/Row'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
diff --git a/src/utils/currencyKey.ts b/src/utils/currencyKey.ts
deleted file mode 100644
index 3c9f378165c..00000000000
--- a/src/utils/currencyKey.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { ChainId, Currency } from '@uniswap/sdk-core'
-import { NATIVE_CHAIN_ID } from 'constants/tokens'
-import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
-import { Chain } from 'graphql/data/Token'
-import { supportedChainIdFromGQLChain } from 'graphql/data/util'
-
-export type CurrencyKey = string
-
-export function buildCurrencyKey(chainId: ChainId, address: string): CurrencyKey {
- // We lowercase for compatibility/indexability between gql tokens and sdk currencies
- return `${chainId}-${address.toLowerCase()}`
-}
-
-export function currencyKey(currency: Currency): CurrencyKey {
- return buildCurrencyKey(currency.chainId, currency.isToken ? currency.address : NATIVE_CHAIN_ID)
-}
-
-export function currencyKeyFromGraphQL(contract: {
- address?: string
- chain: Chain
- standard?: TokenStandard
-}): CurrencyKey {
- const chainId = supportedChainIdFromGQLChain(contract.chain)
- const address = contract.standard === TokenStandard.Native ? NATIVE_CHAIN_ID : contract.address
- if (!address) throw new Error('Non-native token missing address')
- if (!chainId) throw new Error('Unsupported chain from pools query')
- return buildCurrencyKey(chainId, address)
-}