From f229d0e3509b9b5e4b332b9e5a51ba774f3ec040 Mon Sep 17 00:00:00 2001 From: lelenei <72531217+lelenei@users.noreply.github.com> Date: Sun, 5 Dec 2021 19:33:41 +0800 Subject: [PATCH 01/24] feat: an united nft service --- packages/mask/.webpack/config.ts | 2 +- packages/mask/package.json | 1 + .../src/plugins/Avatar/SNSAdaptor/AddNFT.tsx | 2 +- .../mask/src/plugins/Avatar/utils/index.ts | 80 +---- .../Collectible/SNSAdaptor/ActionBar.tsx | 8 +- .../Collectible/SNSAdaptor/ArticleTab.tsx | 4 +- .../Collectible/SNSAdaptor/CheckoutDialog.tsx | 36 +- .../Collectible/SNSAdaptor/CheckoutOrder.tsx | 9 +- .../Collectible/SNSAdaptor/Collectible.tsx | 12 +- .../SNSAdaptor/CollectibleProviderIcon.tsx | 9 +- .../Collectible/SNSAdaptor/HistoryTab/Row.tsx | 20 +- .../SNSAdaptor/HistoryTab/index.tsx | 16 +- .../SNSAdaptor/ListingByHighestBidCard.tsx | 41 ++- .../SNSAdaptor/ListingByPriceCard.tsx | 39 ++- .../Collectible/SNSAdaptor/ListingTab.tsx | 17 +- .../SNSAdaptor/MakeOfferDialog.tsx | 37 ++- .../Collectible/SNSAdaptor/OfferTab.tsx | 19 +- .../Collectible/SNSAdaptor/OrderRow.tsx | 38 ++- .../Collectible/SNSAdaptor/PostInspector.tsx | 1 + .../SNSAdaptor/PostListingDialog.tsx | 2 +- .../plugins/Collectible/SNSAdaptor/index.tsx | 9 +- .../src/plugins/Collectible/apis/index.ts | 3 +- .../src/plugins/Collectible/apis/opensea.ts | 108 ------ .../Collectible/apis/openseaFromSDK.ts | 57 ++++ .../src/plugins/Collectible/apis/rarible.ts | 159 --------- .../mask/src/plugins/Collectible/helpers.ts | 4 +- .../src/plugins/Collectible/hooks/useAsset.ts | 153 --------- .../Collectible/hooks/useAssetOrder.ts | 15 +- .../Collectible/hooks/useCollectibleState.ts | 52 +-- .../plugins/Collectible/hooks/useEvents.ts | 127 ------- .../src/plugins/Collectible/pipes/index.ts | 11 +- .../mask/src/plugins/Collectible/settings.ts | 6 +- .../src/plugins/Collectible/types/index.ts | 96 +----- .../mask/src/plugins/Collectible/utils.ts | 4 +- packages/mask/src/plugins/EVM/base.ts | 3 +- packages/mask/src/plugins/EVM/hooks/index.ts | 5 + .../mask/src/plugins/EVM/hooks/useAsset.ts | 30 ++ .../mask/src/plugins/EVM/hooks/useHistory.ts | 9 + .../src/plugins/EVM/hooks/useNFTBalance.ts | 11 + .../mask/src/plugins/EVM/hooks/useNFTs.ts | 9 + .../mask/src/plugins/EVM/hooks/useOrders.ts | 12 + packages/mask/src/plugins/EVM/index.ts | 1 + packages/mask/src/plugins/EVM/services.ts | 1 - .../plugins/EVM/services/Asset/getAsset.ts | 27 ++ .../EVM/services/Asset/getCollections.ts | 23 ++ .../plugins/EVM/services/Asset/getHistory.ts | 27 ++ .../plugins/EVM/services/Asset/getListings.ts | 27 ++ .../plugins/EVM/services/Asset/getOrder.ts | 29 ++ .../src/plugins/EVM/services/Asset/index.ts | 5 + .../src/plugins/EVM/services/NFT/getNFT.ts | 26 ++ .../plugins/EVM/services/NFT/getNFTBalance.ts | 26 ++ .../EVM/services/NFT/getNFTContract.ts | 21 ++ .../src/plugins/EVM/services/NFT/getNFTs.ts | 25 ++ .../plugins/EVM/services/NFT/getNFTsPaged.ts | 28 ++ .../src/plugins/EVM/services/NFT/index.ts | 5 + .../mask/src/plugins/EVM/services/index.ts | 2 + .../src/plugins/EVM/{apis => types}/index.ts | 0 packages/mask/src/plugins/EVM/utils/index.ts | 1 + .../SNSAdaptor/SelectNftContractDialog.tsx | 4 +- .../Wallet/hooks/useNFTscanFindAssets.ts | 9 - .../src/plugins/Wallet/services/assets.ts | 50 +-- .../mask/src/plugins/Wallet/services/index.ts | 3 +- packages/mask/src/tsconfig.json | 1 + .../src/web3/UI/ERC721ContractSelectPanel.tsx | 12 +- packages/web3-providers/NFTScan/constants.ts | 4 + packages/web3-providers/NFTScan/index.ts | 173 ++++++++++ packages/web3-providers/NFTScan/types.ts | 28 ++ packages/web3-providers/index.ts | 5 + packages/web3-providers/opensea/constants.ts | 4 + packages/web3-providers/opensea/index.ts | 314 +++++++++++++++++- packages/web3-providers/opensea/types.ts | 176 ++++++++++ packages/web3-providers/package.json | 4 + packages/web3-providers/rarible/constants.ts | 1 + packages/web3-providers/rarible/index.ts | 311 ++++++++++++++--- packages/web3-providers/rarible/types.ts | 16 +- packages/web3-providers/tsconfig.json | 5 +- packages/web3-providers/types.ts | 137 ++++++++ packages/web3-providers/utils/index.ts | 36 ++ packages/web3-shared/evm/pipes/index.ts | 12 + packages/web3-shared/evm/types/index.ts | 4 +- pnpm-lock.yaml | 6 + 81 files changed, 1860 insertions(+), 1005 deletions(-) delete mode 100644 packages/mask/src/plugins/Collectible/apis/opensea.ts create mode 100644 packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts delete mode 100644 packages/mask/src/plugins/Collectible/apis/rarible.ts delete mode 100644 packages/mask/src/plugins/Collectible/hooks/useAsset.ts delete mode 100644 packages/mask/src/plugins/Collectible/hooks/useEvents.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useAsset.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useHistory.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useNFTs.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useOrders.ts delete mode 100644 packages/mask/src/plugins/EVM/services.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getAsset.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getCollections.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getHistory.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getListings.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getOrder.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/index.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFT.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts create mode 100644 packages/mask/src/plugins/EVM/services/NFT/index.ts create mode 100644 packages/mask/src/plugins/EVM/services/index.ts rename packages/mask/src/plugins/EVM/{apis => types}/index.ts (100%) create mode 100644 packages/mask/src/plugins/EVM/utils/index.ts delete mode 100644 packages/mask/src/plugins/Wallet/hooks/useNFTscanFindAssets.ts create mode 100644 packages/web3-providers/NFTScan/constants.ts create mode 100644 packages/web3-providers/NFTScan/index.ts create mode 100644 packages/web3-providers/NFTScan/types.ts create mode 100644 packages/web3-providers/index.ts create mode 100644 packages/web3-providers/opensea/constants.ts create mode 100644 packages/web3-providers/opensea/types.ts create mode 100644 packages/web3-providers/types.ts create mode 100644 packages/web3-providers/utils/index.ts diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index 7e2832200e66..7b2def15d836 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -71,7 +71,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration { '@masknet/theme': join(__dirname, '../../theme/src/'), '@masknet/icons': join(__dirname, '../../icons/index.ts'), '@masknet/web3-kit': join(__dirname, '../../web3-kit/src/'), - '@masknet/web3-providers': join(__dirname, '../../web3-providers/src/'), + '@masknet/web3-providers': join(__dirname, '../../web3-providers'), '@masknet/web3-shared-evm': join(__dirname, '../../web3-shared/evm/'), '@masknet/web3-shared-flow': join(__dirname, '../../web3-shared/flow/'), '@masknet/web3-shared-solana': join(__dirname, '../../web3-shared/solana/'), diff --git a/packages/mask/package.json b/packages/mask/package.json index 953f57a21d96..f24ca0165dad 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -31,6 +31,7 @@ "@masknet/theme": "workspace:*", "@masknet/web3-contracts": "workspace:*", "@masknet/web3-shared-evm": "workspace:*", + "@masknet/web3-providers": "workspace:*", "@msgpack/msgpack": "^2.7.1", "@servie/events": "^3.0.0", "@sinonjs/text-encoding": "^0.7.1", diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx index 22b08d71ba09..a01ed117f905 100644 --- a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx +++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx @@ -52,7 +52,7 @@ export function AddNFT(props: AddNFTProps) { createNFT(address, tokenId) .then((token) => { - if (!isSameAddress(token.info.owner, account)) { + if (!token || !isSameAddress(token?.info.owner, account)) { setMessage(t('nft_owner_hint')) return } diff --git a/packages/mask/src/plugins/Avatar/utils/index.ts b/packages/mask/src/plugins/Avatar/utils/index.ts index 514184612a36..3f350f8734b9 100644 --- a/packages/mask/src/plugins/Avatar/utils/index.ts +++ b/packages/mask/src/plugins/Avatar/utils/index.ts @@ -1,75 +1,31 @@ import BigNumber from 'bignumber.js' -import { head, isNull } from 'lodash-unified' -import { PluginCollectibleRPC } from '../../Collectible/messages' -import { getLastSalePrice, getOrderUnitPrice, getOrderUSDPrice } from '../../Collectible/utils' -import { ChainId, createERC721Token, EthereumTokenType } from '@masknet/web3-shared-evm' -import type { AssetOrder } from '../../Collectible/types' +import { isNull } from 'lodash-unified' +import { ChainId } from '@masknet/web3-shared-evm' +import { EVM_RPC } from '../../EVM/messages' import Services from '../../../extension/service' +import { getOrderUnitPrice } from '@masknet/web3-providers' export async function getNFT(address: string, tokenId: string) { - const asset = await PluginCollectibleRPC.getAsset(address, tokenId, ChainId.Mainnet) - - let orders: AssetOrder[] = [] - if (asset.sell_orders?.length) { - orders = asset.sell_orders - } else if (asset.orders?.length) { - orders = asset.orders - } else if (asset.buy_orders?.length) { - orders = asset.buy_orders - } - - const order = head( - orders.sort((a, b) => - new BigNumber( - getOrderUSDPrice( - b.current_price, - b.payment_token_contract?.usd_price, - b.payment_token_contract?.decimals, - ) ?? 0, - ) - .minus( - getOrderUSDPrice( - a.current_price, - a.payment_token_contract?.usd_price, - a.payment_token_contract?.decimals, - ) ?? 0, - ) - .toNumber(), - ), - ) + const asset = await EVM_RPC.getAsset(address, tokenId, ChainId.Mainnet) return { - amount: order - ? new BigNumber( - getOrderUnitPrice(order.current_price, order.payment_token_contract?.decimals, order.quantity) ?? 0, - ).toFixed() - : getLastSalePrice(asset.last_sale) ?? '0', - name: asset.name, - symbol: order?.payment_token_contract?.symbol ?? asset.last_sale?.payment_token?.symbol ?? 'ETH', - image: asset.image_url ?? asset.image_preview_url ?? '', - owner: asset.owner.address, + amount: new BigNumber( + getOrderUnitPrice( + asset?.desktopOrder?.current_price, + asset?.desktopOrder?.payment_token_contract?.decimals ?? 0, + asset?.desktopOrder?.quantity ?? '1', + ) ?? 0, + ).toFixed(), + name: asset?.name ?? '', + symbol: asset?.desktopOrder?.payment_token_contract?.symbol ?? 'ETH', + image: asset?.image_url ?? '', + owner: asset?.top_ownerships[0].owner.address ?? '', } } export async function createNFT(address: string, tokenId: string) { - const asset = await PluginCollectibleRPC.getAsset(address, tokenId, ChainId.Mainnet) - const token = createERC721Token( - { - chainId: ChainId.Mainnet, - type: EthereumTokenType.ERC721, - name: asset.asset_contract.name, - symbol: asset.asset_contract.token_symbol, - address: asset.asset_contract.address, - }, - { - name: asset.name, - description: asset.description, - image: asset.image_url ?? asset.image_preview_url ?? '', - owner: asset.top_ownerships[0].owner.address, - }, - tokenId, - ) - return token + const nft = await EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet) + return nft } export async function getImage(image: string): Promise { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx index bde36cc658c0..08a6c02d00f4 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx @@ -7,6 +7,7 @@ import { useControlledDialog } from '../../../utils/hooks/useControlledDialog' import { MakeOfferDialog } from './MakeOfferDialog' import { PostListingDialog } from './PostListingDialog' import { CheckoutDialog } from './CheckoutDialog' +import { useAccount } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => { return { @@ -26,7 +27,8 @@ export interface ActionBarProps {} export function ActionBar(props: ActionBarProps) { const { t } = useI18N() const { classes } = useStyles() - const { asset, token, assetOrder } = CollectibleState.useContainer() + const account = useAccount() + const { asset, assetOrder, token } = CollectibleState.useContainer() const { open: openCheckoutDialog, @@ -54,7 +56,7 @@ export function ActionBar(props: ActionBarProps) { {t('plugin_collectible_place_bid')} ) : null} - {!asset.value.is_owner && !asset.value.is_auction && assetOrder.value ? ( + {!asset.value.is_owner && !asset.value.is_auction && asset.value?.desktopOrder ? ( ) : null} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx index a71f6e132023..c395e666196d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ArticleTab.tsx @@ -47,10 +47,10 @@ export function ArticleTab(props: ArticleTabProps) {
{asset.value.animation_url ? ( - + ) : ( - + )}
diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/CheckoutDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/CheckoutDialog.tsx index 7f416c601a85..e09181331959 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/CheckoutDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/CheckoutDialog.tsx @@ -10,7 +10,7 @@ import { Typography, Link, } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, useCustomSnackbar } from '@masknet/theme' import { Trans } from 'react-i18next' import { useAccount } from '@masknet/web3-shared-evm' import { InjectedDialog } from '../../../components/shared/InjectedDialog' @@ -19,10 +19,10 @@ import { useI18N } from '../../../utils' import { useRemoteControlledDialog } from '@masknet/shared' import ActionButton, { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' -import type { useAsset } from '../hooks/useAsset' import { PluginCollectibleRPC } from '../messages' import { PluginTraderMessages } from '../../Trader/messages' import { CheckoutOrder } from './CheckoutOrder' +import type { useAsset } from '../../EVM/hooks/useAsset' import type { useAssetOrder } from '../hooks/useAssetOrder' const useStyles = makeStyles()((theme) => { @@ -55,16 +55,16 @@ const useStyles = makeStyles()((theme) => { export interface CheckoutDialogProps { asset?: ReturnType - assetOrder?: ReturnType + order: ReturnType open: boolean onClose: () => void } export function CheckoutDialog(props: CheckoutDialogProps) { - const { asset, open, onClose, assetOrder } = props + const { asset, open, onClose, order } = props const isAuction = asset?.value?.is_auction ?? false const isVerified = asset?.value?.is_verified ?? false - + const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -76,13 +76,23 @@ export function CheckoutDialog(props: CheckoutDialogProps) { const onCheckout = useCallback(async () => { if (!asset?.value) return if (!asset.value.token_id || !asset.value.token_address) return - if (!assetOrder?.value) return - await PluginCollectibleRPC.fulfillOrder({ - order: assetOrder.value, - accountAddress: account, - recipientAddress: account, - }) - }, [asset?.value, account, assetOrder?.value]) + if (!order.value) return + try { + await PluginCollectibleRPC.fulfillOrder({ + order: order.value, + accountAddress: account, + recipientAddress: account, + }) + } catch (error) { + if (error instanceof Error) { + showSnackbar(error.message, { + variant: 'error', + preventDuplicate: true, + }) + } + throw error + } + }, [asset?.value, account, showSnackbar, order?.value]) const { openDialog: openSwapDialog } = useRemoteControlledDialog(PluginTraderMessages.swapDialogUpdated) @@ -103,7 +113,7 @@ export function CheckoutDialog(props: CheckoutDialogProps) { )} - + {isVerified ? null : ( <> ({ @@ -25,8 +24,8 @@ export interface CheckoutOrderProps { export function CheckoutOrder(props: CheckoutOrderProps) { const { t } = useI18N() - const { asset, assetOrder } = props - const order = assetOrder?.value as Order | undefined + const { asset } = props + const order = asset?.value?.desktopOrder const { classes } = useStyles() const chainId = useChainId() @@ -45,7 +44,7 @@ export function CheckoutOrder(props: CheckoutOrderProps) {
- +
{asset.value.collection_name ?? ''} {asset.value.token_address && asset.value.token_id ? ( diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx index 92c36412a619..c845fb6c89a8 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx @@ -30,13 +30,13 @@ import { CollectibleState } from '../hooks/useCollectibleState' import { CollectibleCard } from './CollectibleCard' import { CollectibleProviderIcon } from './CollectibleProviderIcon' import { PluginSkeleton } from './PluginSkeleton' -import { CollectibleProvider, CollectibleTab } from '../types' +import { CollectibleTab } from '../types' import { currentCollectibleProviderSettings } from '../settings' import { MaskTextIcon } from '../../../resources/MaskIcon' import { resolveAssetLinkOnOpenSea, resolveCollectibleProviderName } from '../pipes' import { Markdown } from '../../Snapshot/SNSAdaptor/Markdown' import { ActionBar } from './ActionBar' -import { useChainId } from '@masknet/web3-shared-evm' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { getEnumAsArray } from '@dimensiondev/kit' import { FootnoteMenu, FootnoteMenuOption } from '../../Trader/SNSAdaptor/trader/FootnoteMenu' @@ -143,16 +143,16 @@ export function Collectible(props: CollectibleProps) { const { asset, provider, tabIndex, setTabIndex } = CollectibleState.useContainer() //#region sync with settings - const collectibleProviderOptions = getEnumAsArray(CollectibleProvider) + const collectibleProviderOptions = getEnumAsArray(NonFungibleAssetProvider) const onDataProviderChange = useCallback((option: FootnoteMenuOption) => { - currentCollectibleProviderSettings.value = option.value as CollectibleProvider + currentCollectibleProviderSettings.value = option.value as NonFungibleAssetProvider }, []) //#endregion //#region provider switcher const CollectibleProviderSwitcher = useSettingsSwitcher( currentCollectibleProviderSettings, - getEnumAsArray(CollectibleProvider).map((x) => x.value), + getEnumAsArray(NonFungibleAssetProvider).map((x) => x.value), resolveCollectibleProviderName, ) //#endregion @@ -311,7 +311,7 @@ export function Collectible(props: CollectibleProps) { )} - {provider === CollectibleProvider.OPENSEA ? : null} + {provider === NonFungibleAssetProvider.OPENSEA ? : null} ) } diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/CollectibleProviderIcon.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/CollectibleProviderIcon.tsx index 8d44ff92dc3c..9098ddd5e17b 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/CollectibleProviderIcon.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/CollectibleProviderIcon.tsx @@ -2,7 +2,7 @@ import { makeStyles } from '@masknet/theme' import { OpenSeaIcon } from '../../../resources/OpenSeaIcon' import { RaribleIcon } from '../../../resources/RaribleIcon' import { unreachable } from '@dimensiondev/kit' -import { CollectibleProvider } from '../types' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' const useStyles = makeStyles()({ opensea: { @@ -18,15 +18,16 @@ const useStyles = makeStyles()({ }) export interface CollectibleProviderIconProps { - provider: CollectibleProvider + provider: NonFungibleAssetProvider } export function CollectibleProviderIcon(props: CollectibleProviderIconProps) { const { classes } = useStyles() switch (props.provider) { - case CollectibleProvider.OPENSEA: + case NonFungibleAssetProvider.NFTSCAN: + case NonFungibleAssetProvider.OPENSEA: return - case CollectibleProvider.RARIBLE: + case NonFungibleAssetProvider.RARIBLE: return default: unreachable(props.provider) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx index b4673b7baa1f..582a237e530d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx @@ -2,14 +2,16 @@ import { Avatar, Link, TableCell, TableRow, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' import LinkIcon from '@mui/icons-material/Link' import { FormattedBalance } from '@masknet/shared' -import { formatBalance } from '@masknet/web3-shared-evm' +import { formatBalance, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { formatElapsed } from '../../../Wallet/formatter' import { useMemo } from 'react' -import { CollectibleProvider, NFTHistory, OpenSeaAssetEventType, RaribleEventType } from '../../types' import { CollectibleState } from '../../hooks/useCollectibleState' import { resolveOpenSeaAssetEventType, resolveRaribleAssetEventType } from '../../pipes' import { Account } from '../Account' -import { getOrderUnitPrice } from '../../utils' +import type { NFTHistory } from '@masknet/web3-providers' +import { getOrderUnitPrice } from '@masknet/web3-providers' +import type { OpenSeaAssetEventType } from '../../types/opensea' +import type { RaribleEventType } from '../../types' const useStyles = makeStyles()((theme) => { return { @@ -50,8 +52,7 @@ export function Row({ event, isDifferenceToken }: Props) { const { provider } = CollectibleState.useContainer() const unitPrice = useMemo(() => { - if (provider === CollectibleProvider.RARIBLE || !isDifferenceToken || !event.price) return null - + if (provider === NonFungibleAssetProvider.RARIBLE || !isDifferenceToken || !event.price) return null return getOrderUnitPrice(event.price.price ?? 0, event.price.paymentToken?.decimals, event.price.quantity) }, [event, isDifferenceToken, provider]) @@ -59,7 +60,7 @@ export function Row({ event, isDifferenceToken }: Props) { - {provider === CollectibleProvider.OPENSEA + {provider === NonFungibleAssetProvider.OPENSEA ? resolveOpenSeaAssetEventType( event.eventType as OpenSeaAssetEventType, event.accountPair.from?.username, @@ -74,9 +75,9 @@ export function Row({ event, isDifferenceToken }: Props) { {event.price?.asset?.image_url && ( {event.price.paymentToken?.symbol} )} @@ -89,7 +90,6 @@ export function Row({ event, isDifferenceToken }: Props) { @@ -97,7 +97,7 @@ export function Row({ event, isDifferenceToken }: Props) { ) : ( - {event.price && provider === CollectibleProvider.OPENSEA + {event.price && provider === NonFungibleAssetProvider.OPENSEA ? formatBalance(event.price.quantity, event.price?.asset?.decimals ?? 0) : event.price?.quantity ?? ''} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx index cfc10e0b7433..457488d6d0f7 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx @@ -5,9 +5,9 @@ import { useI18N } from '../../../../utils' import { CollectibleTab } from '../CollectibleTab' import { CollectibleState } from '../../hooks/useCollectibleState' import { Row } from './Row' -import { CollectibleProvider } from '../../types' import { TableListPagination } from '../Pagination' import { LoadingTable } from '../LoadingTable' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => { return { @@ -43,14 +43,14 @@ export function HistoryTab(props: HistoryTabProps) { //#region If there is a different asset, the unit price and quantity should be displayed const isDifferenceToken = useMemo(() => { - if (provider === CollectibleProvider.OPENSEA) - return events.value?.data.some((item) => item.price?.paymentToken?.symbol !== 'ETH') + if (provider === NonFungibleAssetProvider.OPENSEA) + return events.value?.some((item) => item.price?.asset?.asset_contract.symbol !== 'ETH') else return false }, [events.value, provider]) //#endregion if (events.loading) return - if (!events.value || events.error || !events.value?.data.length) + if (!events.value || events.error || !events.value?.length) return ( @@ -72,7 +72,7 @@ export function HistoryTab(props: HistoryTabProps) { handlePrevClick={() => setEventPage((prev) => prev - 1)} handleNextClick={() => setEventPage((prev) => prev + 1)} prevDisabled={eventPage === 0} - nextDisabled={!events.value?.hasNextPage} + nextDisabled={false} page={eventPage} pageCount={10} /> @@ -99,16 +99,16 @@ export function HistoryTab(props: HistoryTabProps) { - {events.value.data.map((order) => ( + {events.value.map((order) => ( ))} - {(provider === CollectibleProvider.OPENSEA && events.value.data.length) || eventPage > 0 ? ( + {(provider === NonFungibleAssetProvider.OPENSEA && events.value.length) || eventPage > 0 ? ( setEventPage((prev) => prev - 1)} handleNextClick={() => setEventPage((prev) => prev + 1)} prevDisabled={eventPage === 0} - nextDisabled={!events.value.hasNextPage} + nextDisabled={false} page={eventPage} pageCount={10} /> diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx index 59a3f0a83b5d..7caeda5edb21 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Card, CardActions, CardContent } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, useCustomSnackbar } from '@masknet/theme' import { EthereumTokenType, FungibleTokenDetailed, @@ -16,9 +16,9 @@ import { ActionButtonPromise } from '../../../extension/options-page/DashboardCo import { SelectTokenAmountPanel } from '../../ITO/SNSAdaptor/SelectTokenAmountPanel' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { DateTimePanel } from '../../../web3/UI/DateTimePanel' -import type { useAsset } from '../hooks/useAsset' import { PluginCollectibleRPC } from '../messages' import { toAsset, toUnixTimestamp } from '../helpers' +import type { useAsset } from '../../EVM/hooks/useAsset' const useStyles = makeStyles()((theme) => ({ footer: { @@ -49,7 +49,7 @@ export interface ListingByHighestBidCardProps { export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { const { asset, tokenWatched, paymentTokens, open, onClose } = props const { amount, token, balance, setAmount, setToken } = tokenWatched - + const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -71,20 +71,27 @@ export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.ERC20) return - await PluginCollectibleRPC.createSellOrder({ - asset: toAsset({ - tokenId: asset.value.token_id, - tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract.schema_name, - }), - accountAddress: account, - startAmount: Number.parseFloat(amount), - expirationTime: toUnixTimestamp(expirationDateTime), - englishAuctionReservePrice: Number.parseFloat(reservePrice), - waitForHighestBid: true, - paymentTokenAddress: token.value.address, // english auction must be erc20 token - }) - }, [asset?.value, token, amount, account, reservePrice, expirationDateTime]) + try { + await PluginCollectibleRPC.createSellOrder({ + asset: toAsset({ + tokenId: asset.value.token_id, + tokenAddress: asset.value.token_address, + schemaName: asset.value.asset_contract?.schemaName, + }), + accountAddress: account, + startAmount: Number.parseFloat(amount), + expirationTime: toUnixTimestamp(expirationDateTime), + englishAuctionReservePrice: Number.parseFloat(reservePrice), + waitForHighestBid: true, + paymentTokenAddress: token.value.address, // english auction must be erc20 token + }) + } catch (error) { + if (error instanceof Error) { + showSnackbar(error.message, { variant: 'error', preventDuplicate: true }) + } + throw error + } + }, [asset?.value, token, amount, account, reservePrice, expirationDateTime, showSnackbar]) useEffect(() => { setAmount('') diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx index 90c68d1116cf..feb142cd16df 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx @@ -1,7 +1,7 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { EthereumAddress } from 'wallet.ts' import { Box, Card, CardActions, CardContent, Checkbox, FormControlLabel, TextField, Typography } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, useCustomSnackbar } from '@masknet/theme' import { EthereumTokenType, FungibleTokenDetailed, @@ -19,7 +19,7 @@ import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWallet import { DateTimePanel } from '../../../web3/UI/DateTimePanel' import { PluginCollectibleRPC } from '../messages' import { toAsset, toUnixTimestamp } from '../helpers' -import type { useAsset } from '../hooks/useAsset' +import type { useAsset } from '../../EVM/hooks/useAsset' const useStyles = makeStyles()((theme) => { return { @@ -58,7 +58,7 @@ export interface ListingByPriceCardProps { export function ListingByPriceCard(props: ListingByPriceCardProps) { const { asset, tokenWatched, paymentTokens, open, onClose } = props const { amount, token, balance, setAmount, setToken } = tokenWatched - + const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -100,19 +100,26 @@ export function ListingByPriceCard(props: ListingByPriceCardProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.Native && token.value.type !== EthereumTokenType.ERC20) return - await PluginCollectibleRPC.createSellOrder({ - asset: toAsset({ - tokenId: asset.value.token_id, - tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract.schema_name, - }), - accountAddress: account, - startAmount: Number.parseFloat(amount), - endAmount: endingPriceChecked && endingAmount ? Number.parseFloat(endingAmount) : undefined, - listingTime: futureTimeChecked ? toUnixTimestamp(scheduleTime) : undefined, - expirationTime: endingPriceChecked ? toUnixTimestamp(expirationTime) : undefined, - buyerAddress: privacyChecked ? buyerAddress : undefined, - }) + try { + await PluginCollectibleRPC.createSellOrder({ + asset: toAsset({ + tokenId: asset.value.token_id, + tokenAddress: asset.value.token_address, + schemaName: asset.value.asset_contract?.schemaName, + }), + accountAddress: account, + startAmount: Number.parseFloat(amount), + endAmount: endingPriceChecked && endingAmount ? Number.parseFloat(endingAmount) : undefined, + listingTime: futureTimeChecked ? toUnixTimestamp(scheduleTime) : undefined, + expirationTime: endingPriceChecked ? toUnixTimestamp(expirationTime) : undefined, + buyerAddress: privacyChecked ? buyerAddress : undefined, + }) + } catch (error) { + if (error instanceof Error) { + showSnackbar(error.message, { variant: 'error', preventDuplicate: true }) + } + throw error + } }, [ asset?.value, token, diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx index 4e80f44ad277..d10ae6561fe2 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx @@ -7,9 +7,8 @@ import { CollectibleState } from '../hooks/useCollectibleState' import { CollectibleTab } from './CollectibleTab' import { OrderRow } from './OrderRow' import { TableListPagination } from './Pagination' -import { CollectibleProvider } from '../types' import { LoadingTable } from './LoadingTable' -import { isZero, useAccount } from '@masknet/web3-shared-evm' +import { isZero, NonFungibleAssetProvider, useAccount } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => { return { @@ -43,14 +42,14 @@ export function ListingTab() { const { token, asset, provider, orders, orderPage, setOrderPage } = CollectibleState.useContainer() const isDifferenceToken = useMemo(() => { - if (provider === CollectibleProvider.OPENSEA) { + if (provider === NonFungibleAssetProvider.OPENSEA) { return ( - orders.some( + orders.value?.some( (item) => (item.payment_token_contract?.symbol !== 'WETH' && item.payment_token_contract?.symbol !== 'ETH') || (item.quantity && new BigNumber(item.quantity).toString() !== '1'), - ) && orders.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 + ) && orders.value?.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 ) } else { return false @@ -58,8 +57,8 @@ export function ListingTab() { }, [provider, orders]) const dataSource = useMemo(() => { - if (!asset.value || !orders.length) return [] - return orders.sort((a, b) => { + if (!orders.value || !orders.value?.length) return [] + return orders.value.sort((a, b) => { const current = new BigNumber(a.current_price ?? 0) const next = new BigNumber(b.current_price ?? 0) if (current.isLessThan(next)) return -1 @@ -114,7 +113,7 @@ export function ListingTab() { ) : ( <> {t('plugin_collectible_price')} - {provider === CollectibleProvider.OPENSEA ? ( + {provider === NonFungibleAssetProvider.OPENSEA ? ( {t('plugin_collectible_expiration')} ) : null} @@ -126,7 +125,7 @@ export function ListingTab() { ))} - {(provider === CollectibleProvider.OPENSEA && dataSource.length) || orderPage > 0 ? ( + {(provider === NonFungibleAssetProvider.OPENSEA && dataSource.length) || orderPage > 0 ? ( setOrderPage((prev) => prev - 1)} handleNextClick={() => setOrderPage((prev) => prev + 1)} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 1b9f8133adca..bc6fab6f5409 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -10,7 +10,7 @@ import { Typography, Link, } from '@mui/material' -import { makeStyles } from '@masknet/theme' +import { makeStyles, useCustomSnackbar } from '@masknet/theme' import { first } from 'lodash-unified' import BigNumber from 'bignumber.js' import { @@ -28,12 +28,12 @@ import { UnreviewedWarning } from './UnreviewedWarning' import ActionButton, { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton' import { SelectTokenAmountPanel } from '../../ITO/SNSAdaptor/SelectTokenAmountPanel' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' -import type { useAsset } from '../hooks/useAsset' import { DateTimePanel } from '../../../web3/UI/DateTimePanel' import { PluginCollectibleRPC } from '../messages' import { toAsset, toUnixTimestamp } from '../helpers' import { PluginTraderMessages } from '../../Trader/messages' import { Trans } from 'react-i18next' +import type { useAsset } from '../../EVM/hooks/useAsset' const useStyles = makeStyles()((theme) => { return { @@ -75,7 +75,7 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { const isVerified = asset?.value?.is_verified ?? false const paymentTokens = (isAuction ? asset?.value?.order_payment_tokens : asset?.value?.offer_payment_tokens) ?? [] const selectedPaymentToken = first(paymentTokens) - + const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -92,18 +92,25 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.Native && token.value.type !== EthereumTokenType.ERC20) return - await PluginCollectibleRPC.createBuyOrder({ - asset: toAsset({ - tokenId: asset.value.token_id, - tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract.schema_name, - }), - accountAddress: account, - startAmount: Number.parseFloat(amount), - expirationTime: !isAuction ? toUnixTimestamp(expirationDateTime) : undefined, - paymentTokenAddress: token.value.type === EthereumTokenType.Native ? undefined : token.value.address, - }) - }, [asset?.value, token, account, amount, expirationDateTime, isAuction]) + try { + await PluginCollectibleRPC.createBuyOrder({ + asset: toAsset({ + tokenId: asset.value.token_id, + tokenAddress: asset.value.token_address, + schemaName: asset.value.asset_contract?.schemaName, + }), + accountAddress: account, + startAmount: Number.parseFloat(amount), + expirationTime: !isAuction ? toUnixTimestamp(expirationDateTime) : undefined, + paymentTokenAddress: token.value.type === EthereumTokenType.Native ? undefined : token.value.address, + }) + } catch (error) { + if (error instanceof Error) { + showSnackbar(error.message, { variant: 'error', preventDuplicate: true }) + } + throw error + } + }, [asset?.value, token, account, amount, expirationDateTime, isAuction, showSnackbar]) const { openDialog: openSwapDialog } = useRemoteControlledDialog(PluginTraderMessages.swapDialogUpdated) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx index 42dc7f5b08d5..e4f6621ab30d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx @@ -7,9 +7,8 @@ import { CollectibleState } from '../hooks/useCollectibleState' import { CollectibleTab } from './CollectibleTab' import { OrderRow } from './OrderRow' import { TableListPagination } from './Pagination' -import { CollectibleProvider } from '../types' import { LoadingTable } from './LoadingTable' -import { isZero } from '@masknet/web3-shared-evm' +import { isZero, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => { return { @@ -42,14 +41,14 @@ export function OfferTab() { const { asset, provider, offers, offerPage, setOfferPage } = CollectibleState.useContainer() const isDifferenceToken = useMemo(() => { - if (provider === CollectibleProvider.OPENSEA) { + if (provider === NonFungibleAssetProvider.OPENSEA) { return ( - offers.some( + offers.value?.some( (item) => (item.payment_token_contract?.symbol !== 'WETH' && item.payment_token_contract?.symbol !== 'ETH') || (item.quantity && new BigNumber(item.quantity).toString() !== '1'), - ) && offers.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 + ) && offers.value.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 ) } else { return false @@ -57,12 +56,12 @@ export function OfferTab() { }, [provider, offers]) const dataSource = useMemo(() => { - if (!offers.length) return [] - return offers + if (!offers.value?.length) return [] + return offers.value }, [offers]) if (asset.loading) return - if (!offers.length || asset.error || !dataSource.length) + if (!offers.value?.length || asset.error || !dataSource.length) return (
@@ -105,7 +104,7 @@ export function OfferTab() { ) : ( <> {t('plugin_collectible_price')} - {provider === CollectibleProvider.OPENSEA ? ( + {provider === NonFungibleAssetProvider.OPENSEA ? ( {t('plugin_collectible_expiration')} ) : null} @@ -117,7 +116,7 @@ export function OfferTab() { ))} - {(provider === CollectibleProvider.OPENSEA && dataSource.length) || offerPage > 0 ? ( + {(provider === NonFungibleAssetProvider.OPENSEA && dataSource.length) || offerPage > 0 ? ( setOfferPage((prev) => prev - 1)} handleNextClick={() => setOfferPage((prev) => prev + 1)} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index 6d4d282d2630..7e306723e6a8 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -2,13 +2,20 @@ import { useMemo } from 'react' import BigNumber from 'bignumber.js' import { Avatar, Link, TableCell, TableRow, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { AssetOrder, CollectibleProvider } from '../types' + import formatDistanceToNow from 'date-fns/formatDistanceToNow' -import { ChainId, formatBalance, isZero, resolveAddressLinkOnExplorer } from '@masknet/web3-shared-evm' +import { + ChainId, + formatBalance, + isZero, + NonFungibleAssetProvider, + resolveAddressLinkOnExplorer, +} from '@masknet/web3-shared-evm' import { CollectibleState } from '../hooks/useCollectibleState' import { Account } from './Account' import { FormattedBalance } from '@masknet/shared' -import { getOrderUnitPrice } from '../utils' +import { getOrderUnitPrice } from '@masknet/web3-providers' +import type { AssetOrder } from '@masknet/web3-providers' const useStyles = makeStyles()((theme) => { return { @@ -60,9 +67,9 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { const address = order.maker_account?.user?.username || order.maker_account?.address || '' const link = useMemo(() => { - return provider === CollectibleProvider.OPENSEA + return provider === NonFungibleAssetProvider.OPENSEA ? `https://opensea.io/accounts/${address}` - : order.maker_account?.address + : order.maker_account?.link }, [order, provider]) return ( @@ -82,7 +89,7 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { <> - {provider === CollectibleProvider.OPENSEA ? ( + {provider === NonFungibleAssetProvider.OPENSEA ? ( - {provider === CollectibleProvider.OPENSEA ? ( + {provider === NonFungibleAssetProvider.OPENSEA ? ( {order.payment_token_contract?.image_url && ( {order.payment_token_contract?.symbol} @@ -138,20 +145,25 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { order.payment_token_contract?.decimals, order.quantity, )} ${ - provider === CollectibleProvider.OPENSEA + provider === NonFungibleAssetProvider.OPENSEA ? order.payment_token_contract?.symbol ?? '' : 'ETH' }`} - {provider === CollectibleProvider.OPENSEA ? ( + {provider === NonFungibleAssetProvider.OPENSEA ? ( {order.expiration_time && !isZero(order.expiration_time) && - formatDistanceToNow(new Date(order.expiration_time * 1000), { - addSuffix: true, - })} + formatDistanceToNow( + new Date( + new BigNumber(order.expiration_time ?? 0).multipliedBy(1000).toNumber(), + ), + { + addSuffix: true, + }, + )} ) : null} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx index bad1491c86bd..95138445b06d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx @@ -17,6 +17,7 @@ export function PostInspector(props: PostInspectorProps) { chainId: token.chain_id, tokenId: token.token_id, contractAddress: token.address, + provider: token.provider, }}> diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx index 6d7fd0ed3647..29ba310a63ed 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx @@ -5,9 +5,9 @@ import { useI18N } from '../../../utils' import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { ListingByPriceCard } from './ListingByPriceCard' import { ListingByHighestBidCard } from './ListingByHighestBidCard' -import type { useAsset } from '../hooks/useAsset' import { useFungibleTokenWatched } from '@masknet/web3-shared-evm' import { first } from 'lodash-unified' +import type { useAsset } from '../../EVM/hooks/useAsset' const useStyles = makeStyles()((theme) => { return { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx index d421b4d9ac44..99f3b2bafb9b 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx @@ -12,17 +12,18 @@ const sns: Plugin.SNSAdaptor.Definition = { ...base, init(signal) {}, PostInspector: function Component() { - const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.postMentionedLinks()) - + const links = usePostInfoDetails + .postMetadataMentionedLinks() + .concat(usePostInfoDetails.postMentionedLinks()) as string[] const link = uniq(links).find(checkUrl) const asset = getAssetInfoFromURL(link) - return asset ? renderPostInspector(asset) : null + return asset ? renderPostInspector(asset as CollectibleJSON_Payload) : null }, DecryptedInspector: function Component(props) { const collectibleUrl = getRelevantUrl(getTypedMessageContent(props.message)) const asset = getAssetInfoFromURL(collectibleUrl) - return asset ? renderPostInspector(asset) : null + return asset ? renderPostInspector(asset as CollectibleJSON_Payload) : null }, } diff --git a/packages/mask/src/plugins/Collectible/apis/index.ts b/packages/mask/src/plugins/Collectible/apis/index.ts index e21510b9c18c..da51c15f303e 100644 --- a/packages/mask/src/plugins/Collectible/apis/index.ts +++ b/packages/mask/src/plugins/Collectible/apis/index.ts @@ -1,2 +1 @@ -export * from './opensea' -export * from './rarible' +export * from './openseaFromSDK' diff --git a/packages/mask/src/plugins/Collectible/apis/opensea.ts b/packages/mask/src/plugins/Collectible/apis/opensea.ts deleted file mode 100644 index 69b363a0794d..000000000000 --- a/packages/mask/src/plugins/Collectible/apis/opensea.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { head, toString } from 'lodash-unified' -import { OpenSeaPort } from 'opensea-js' -import { ChainId } from '@masknet/web3-shared-evm' -import { request, requestSend } from '../../../extension/background-script/EthereumService' -import { resolveOpenSeaNetwork } from '../pipes' -import { OpenSeaAPI_Key, OpenSeaBaseURL, OpenSeaRinkebyBaseURL, ReferrerAddress } from '../constants' -import { currentChainIdSettings } from '../../Wallet/settings' -import urlcat from 'urlcat' -import type { OpenSeaAssetEvent, OpenSeaResponse } from '../types' - -function createExternalProvider() { - return { - isMetaMask: false, - isStatus: true, - host: '', - path: '', - sendAsync: requestSend, - send: requestSend, - request, - } -} - -async function createOpenSeaPort(chainId?: ChainId) { - return createOpenSeaPortChain(chainId ?? currentChainIdSettings.value) -} - -async function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby) { - return new OpenSeaPort( - createExternalProvider(), - { - networkName: resolveOpenSeaNetwork(chainId), - apiKey: OpenSeaAPI_Key, - }, - console.log, - ) -} - -async function createOpenSeaAPI(chainId: ChainId) { - if (chainId === ChainId.Rinkeby) return OpenSeaRinkebyBaseURL - return OpenSeaBaseURL -} - -export async function getAssetFromSDK(tokenAddress: string, tokenId: string, chainId?: ChainId) { - const _chainId = chainId ?? currentChainIdSettings.value - const sdkResponse = await (await createOpenSeaPort(_chainId)).api.getAsset({ tokenAddress, tokenId }) - return sdkResponse -} - -export async function getAsset(tokenAddress: string, tokenId: string, chainId?: ChainId) { - const _chainId = chainId ?? currentChainIdSettings.value - const fetchResponse = await ( - await fetch( - urlcat(await createOpenSeaAPI(_chainId), '/asset/:tokenAddress/:tokenId', { tokenAddress, tokenId }), - { - mode: 'cors', - headers: { - 'x-api-key': OpenSeaAPI_Key, - }, - }, - ) - ).json() - - const endTime = head<{ closing_date: Date }>( - fetchResponse.orders.filter((item: { side: number; closing_extendable: boolean }) => item.side === 1), - )?.closing_date - - return { - ...fetchResponse, - endTime, - } as OpenSeaResponse -} - -export async function getEvents(asset_contract_address: string, token_id: string, page?: number, size?: number) { - const params = new URLSearchParams() - params.append('asset_contract_address', asset_contract_address) - params.append('token_id', token_id) - params.append('offset', toString(page ?? 0)) - params.append('limit', toString(size ?? 10)) - - const fetchResponse = await ( - await fetch(urlcat(await createOpenSeaAPI(currentChainIdSettings.value), `events?${params.toString()}`), { - mode: 'cors', - headers: { 'x-api-key': OpenSeaAPI_Key }, - }) - ).json() - - const { asset_events }: { asset_events: OpenSeaAssetEvent[] } = fetchResponse - - return asset_events -} - -export async function createBuyOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).createBuyOrder({ - referrerAddress: ReferrerAddress, - ...payload, - }) -} - -export async function createSellOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).createSellOrder(payload) -} - -export async function fulfillOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).fulfillOrder({ - referrerAddress: ReferrerAddress, - ...payload, - }) -} diff --git a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts new file mode 100644 index 000000000000..8842f0b3ba4c --- /dev/null +++ b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts @@ -0,0 +1,57 @@ +import { OpenSeaPort } from 'opensea-js' +import type { ChainId } from '@masknet/web3-shared-evm' +import { request, requestSend } from '../../../extension/background-script/EthereumService' +import { resolveOpenSeaNetwork } from '../pipes' +import { OpenSeaAPI_Key, ReferrerAddress } from '../constants' +import { currentChainIdSettings } from '../../Wallet/settings' + +function createExternalProvider() { + return { + isMetaMask: false, + isStatus: true, + host: '', + path: '', + sendAsync: requestSend, + send: requestSend, + request, + } +} + +async function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby) { + return new OpenSeaPort( + createExternalProvider(), + { + networkName: resolveOpenSeaNetwork(chainId), + apiKey: OpenSeaAPI_Key, + }, + console.log, + ) +} + +async function createOpenSeaPort(chainId?: ChainId) { + return createOpenSeaPortChain(chainId ?? currentChainIdSettings.value) +} + +export async function getAssetFromSDK(tokenAddress: string, tokenId: string, chainId?: ChainId) { + const _chainId = chainId ?? currentChainIdSettings.value + const sdkResponse = await (await createOpenSeaPort(_chainId)).api.getAsset({ tokenAddress, tokenId }) + return sdkResponse +} + +export async function createBuyOrder(payload: Parameters[0]) { + return (await createOpenSeaPort()).createBuyOrder({ + referrerAddress: ReferrerAddress, + ...payload, + }) +} + +export async function createSellOrder(payload: Parameters[0]) { + return (await createOpenSeaPort()).createSellOrder(payload) +} + +export async function fulfillOrder(payload: Parameters[0]) { + return (await createOpenSeaPort()).fulfillOrder({ + referrerAddress: ReferrerAddress, + ...payload, + }) +} diff --git a/packages/mask/src/plugins/Collectible/apis/rarible.ts b/packages/mask/src/plugins/Collectible/apis/rarible.ts deleted file mode 100644 index dfbfc4083160..000000000000 --- a/packages/mask/src/plugins/Collectible/apis/rarible.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { RaribleChainURL, RaribleMainnetURL } from '../constants' -import { compact } from 'lodash-unified' -import { OrderSide } from 'opensea-js/lib/types' -import stringify from 'json-stable-stringify' -import { - Ownership, - RaribleEventType, - RaribleHistory, - RaribleNFTItemMapResponse, - RaribleOfferResponse, - RaribleProfileResponse, -} from '../types' -import { toRaribleImage } from '../helpers' -import { resolveRaribleUserNetwork } from '../pipes' -import { currentChainIdSettings } from '../../Wallet/settings' -import urlcat from 'urlcat' - -async function fetchFromRarible(root: string, subPath: string, config = {} as RequestInit) { - const response = await ( - await fetch(urlcat(root, subPath), { - mode: 'cors', - ...config, - }) - ).json() - - return response as T -} - -export async function getProfilesFromRarible(addresses: (string | undefined)[]) { - return fetchFromRarible(RaribleMainnetURL, 'profiles/list', { - method: 'POST', - body: stringify(addresses), - headers: { - 'content-type': 'application/json', - }, - }) -} - -export async function getNFTItem(tokenAddress: string, tokenId: string) { - const assetResponse = await fetchFromRarible( - RaribleChainURL, - urlcat('/v0.1/nft/items/:tokenAddress::tokenId', { - includeMeta: true, - tokenAddress, - tokenId, - }), - { - method: 'GET', - headers: { - 'content-type': 'application/json', - }, - }, - ) - return assetResponse -} - -export async function getOffersFromRarible(tokenAddress: string, tokenId: string) { - const orders = await fetchFromRarible( - RaribleMainnetURL, - `items/${tokenAddress}:${tokenId}/offers`, - { - method: 'POST', - body: stringify({ size: 20 }), - headers: { - 'content-type': 'application/json', - }, - }, - ) - const profiles = await getProfilesFromRarible(orders.map((item) => item.maker)) - const chainId = currentChainIdSettings.value - return orders.map((order) => { - const ownerInfo = profiles.find((owner) => owner.id === order.maker) - return { - unitPrice: order.buyPriceEth, - hash: order.signature, - makerAccount: { - user: { - username: ownerInfo?.name, - }, - address: ownerInfo?.id, - profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${resolveRaribleUserNetwork(chainId)}${ownerInfo?.id ?? ''}`, - }, - } - }) -} - -export async function getListingsFromRarible(tokenAddress: string, tokenId: string) { - const assets = await fetchFromRarible(RaribleMainnetURL, `items/${tokenAddress}:${tokenId}/ownerships`) - const listings = assets.filter((x) => x.selling) - const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) - const chainId = currentChainIdSettings.value - return listings.map((asset) => { - const ownerInfo = profiles.find((owner) => owner.id === asset.owner) - return { - unitPrice: asset.priceEth, - hash: asset.signature, - makerAccount: { - user: { - username: ownerInfo?.name, - }, - address: ownerInfo?.id, - profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${resolveRaribleUserNetwork(chainId)}${ownerInfo?.id ?? ''}`, - }, - } - }) -} - -export async function getOrderFromRarible(tokenAddress: string, tokenId: string, side: OrderSide) { - switch (side) { - case OrderSide.Buy: - return getOffersFromRarible(tokenAddress, tokenId) - case OrderSide.Sell: - return getListingsFromRarible(tokenAddress, tokenId) - default: - return [] - } -} - -export async function getHistoryFromRarible(tokenAddress: string, tokenId: string) { - let histories = await fetchFromRarible(RaribleMainnetURL, `activity`, { - method: 'POST', - body: stringify({ - // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], - filter: { - '@type': 'by_item', - address: tokenAddress, - tokenId, - }, - size: 100, - }), - headers: { - 'content-type': 'application/json', - }, - }) - - if (histories.length) { - histories = histories.filter((x) => Object.values(RaribleEventType).includes(x['@type'])) - } - - const profiles = await getProfilesFromRarible( - compact([ - ...histories.map((history) => history.owner), - ...histories.map((history) => history.buyer), - ...histories.map((history) => history.from), - ]), - ) - - return histories.map((history) => { - const ownerInfo = profiles.find((profile) => profile.id === history.owner) - const fromInfo = profiles.find((profile) => profile.id === history.buyer || profile.id === history.from) - return { - ...history, - ownerInfo, - fromInfo, - } - }) -} diff --git a/packages/mask/src/plugins/Collectible/helpers.ts b/packages/mask/src/plugins/Collectible/helpers.ts index 8de41acde40a..514e10c56904 100644 --- a/packages/mask/src/plugins/Collectible/helpers.ts +++ b/packages/mask/src/plugins/Collectible/helpers.ts @@ -12,11 +12,11 @@ export const CollectibleMetadataReader = createTypedMessageMetadataReader { - if (!token) return - switch (provider) { - case CollectibleProvider.OPENSEA: - const openSeaResponse = await PluginCollectibleRPC.getAsset(token.contractAddress, token.tokenId) - - const desktopOrder = head( - (openSeaResponse.sell_orders ?? []).sort( - (a, b) => - new BigNumber( - getOrderUnitPrice(a.current_price, a.payment_token_contract?.decimals, a.quantity) ?? 0, - ).toNumber() - - new BigNumber( - getOrderUnitPrice(b.current_price, b.payment_token_contract?.decimals, b.quantity) ?? 0, - ).toNumber(), - ), - ) - - return { - is_verified: ['approved', 'verified'].includes( - openSeaResponse.collection?.safelist_request_status ?? '', - ), - - is_order_weth: isSameAddress(desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS), - is_collection_weth: openSeaResponse.collection.payment_tokens.some( - currySameAddress(WNATIVE_ADDRESS), - ), - is_owner: openSeaResponse.top_ownerships.some((item) => isSameAddress(item.owner.address, account)), - // it's an IOS string as my inspection - is_auction: Date.parse(`${openSeaResponse.endTime ?? ''}Z`) > Date.now(), - image_url: - openSeaResponse.image_url_original ?? - openSeaResponse.image_url ?? - openSeaResponse.image_preview_url ?? - '', - asset_contract: openSeaResponse.asset_contract, - current_price: desktopOrder - ? new BigNumber( - getOrderUnitPrice( - desktopOrder.current_price, - desktopOrder.payment_token_contract?.decimals, - desktopOrder.quantity, - ) ?? 0, - ).toNumber() - : null, - current_symbol: desktopOrder?.payment_token_contract?.symbol ?? 'ETH', - owner: { - ...openSeaResponse.owner, - link: `${OpenSeaAccountURL}${ - openSeaResponse.owner?.user?.username ?? openSeaResponse.owner.address ?? '' - }`, - }, - creator: { - ...openSeaResponse.creator, - link: `${OpenSeaAccountURL}${ - openSeaResponse.creator?.user?.username ?? openSeaResponse.creator?.address ?? '' - }`, - }, - token_id: openSeaResponse.token_id, - token_address: openSeaResponse.token_address ?? openSeaResponse.asset_contract.address, - traits: openSeaResponse.traits, - safelist_request_status: openSeaResponse.collection?.safelist_request_status ?? '', - description: openSeaResponse.description, - name: openSeaResponse.name ?? openSeaResponse.collection.name, - collection_name: openSeaResponse.collection.name, - animation_url: openSeaResponse.animation_url, - end_time: openSeaResponse.endTime - ? new Date(openSeaResponse.endTime) - : desktopOrder - ? toDate(Number.parseInt(desktopOrder.listing_time as unknown as string, 10)) - : null, - order_payment_tokens: desktopOrder?.payment_token_contract - ? [toTokenDetailed(chainId, desktopOrder.payment_token_contract)] - : [], - offer_payment_tokens: uniqBy( - openSeaResponse.collection.payment_tokens.map((x) => toTokenDetailed(chainId, x)), - (x) => x.address.toLowerCase(), - ).filter((x) => x.type === EthereumTokenType.ERC20), - slug: openSeaResponse.collection.slug, - orders: openSeaResponse.orders, - response_: openSeaResponse, - } - case CollectibleProvider.RARIBLE: - const raribleResponse = await PluginCollectibleRPC.getNFTItem(token.contractAddress, token.tokenId) - const owner = first(raribleResponse?.owners) - const creator = first(raribleResponse?.creators) - return { - is_order_weth: false, - is_collection_weth: false, - is_verified: false, - is_owner: false, - is_auction: false, - image_url: raribleResponse?.meta.image.url.PREVIEW, - asset_contract: null, - owner: owner - ? { - address: owner, - profile_img_url: '', - user: { username: owner }, - link: '', - } - : null, - creator: creator - ? { - address: creator.account, - profile_img_url: '', - user: { username: creator.account }, - link: '', - } - : null, - traits: raribleResponse?.meta.attributes.map(({ key, value }) => ({ trait_type: key, value })), - description: raribleResponse?.meta.description ?? '', - name: raribleResponse?.meta.name ?? 'Unknown', - collection_name: '', - animation_url: raribleResponse.meta.animation?.url.PREVIEW, - current_price: 0, - current_symbol: 'ETH', - end_time: null, - order_payment_tokens: [] as FungibleTokenDetailed[], - offer_payment_tokens: [] as FungibleTokenDetailed[], - - slug: '', - response_: raribleResponse, - } - default: - unreachable(provider) - } - }, [toTokenIdentifier(token), account, chainId, provider, WNATIVE_ADDRESS]) -} diff --git a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts index 4edd2bd6a4b2..9dc665b4856b 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts @@ -1,16 +1,17 @@ import { unreachable } from '@dimensiondev/kit' +import { getOrderUnitPrice } from '@masknet/web3-providers' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { BigNumber } from 'bignumber.js' import { head } from 'lodash-unified' import { useAsyncRetry } from 'react-use' import { PluginCollectibleRPC } from '../messages' -import { CollectibleProvider, CollectibleToken } from '../types' -import { getOrderUnitPrice } from '../utils' +import type { CollectibleToken } from '../types' -export function useAssetOrder(provider: CollectibleProvider, token?: CollectibleToken) { +export function useAssetOrder(provider: NonFungibleAssetProvider, token?: CollectibleToken) { return useAsyncRetry(async () => { if (!token) return switch (provider) { - case CollectibleProvider.OPENSEA: + case NonFungibleAssetProvider.OPENSEA: const openSeaResponse = await PluginCollectibleRPC.getAssetFromSDK(token.contractAddress, token.tokenId) const desktopOrder = head( @@ -19,7 +20,7 @@ export function useAssetOrder(provider: CollectibleProvider, token?: Collectible new BigNumber( getOrderUnitPrice( a.currentPrice?.toFixed(), - a.paymentTokenContract?.decimals, + a.paymentTokenContract?.decimals ?? 0, a.quantity.toFixed(), ) ?? 0, ).toNumber() - @@ -34,7 +35,9 @@ export function useAssetOrder(provider: CollectibleProvider, token?: Collectible ) return desktopOrder - case CollectibleProvider.RARIBLE: + case NonFungibleAssetProvider.RARIBLE: + return + case NonFungibleAssetProvider.NFTSCAN: return default: unreachable(provider) diff --git a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts index c07d658f805d..726b5f7828d0 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts @@ -1,53 +1,59 @@ -import { useMemo, useState } from 'react' +import { useRef, useState } from 'react' import { createContainer } from 'unstated-next' import { OrderSide } from 'opensea-js/lib/types' -import { useValueRef } from '@masknet/shared' import { currentCollectibleProviderSettings } from '../settings' import { CollectibleTab, CollectibleToken } from '../types' -import { useAsset } from './useAsset' -import { useEvents } from './useEvents' +import { useAsset, useHistory, useOrders } from '../../EVM/hooks' import { useAssetOrder } from './useAssetOrder' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm/types' function useCollectibleState(token?: CollectibleToken) { const [tabIndex, setTabIndex] = useState(CollectibleTab.ARTICLE) - const provider = useValueRef(currentCollectibleProviderSettings) - const asset = useAsset(provider, token) + currentCollectibleProviderSettings.value = token?.provider ?? NonFungibleAssetProvider.OPENSEA - //#region asset order - const assetOrder = useAssetOrder(provider, token) + //const provider = useValueRef(currentCollectibleProviderSettings) + const asset = useAsset( + token?.contractAddress ?? '', + token?.tokenId ?? '', + token?.provider ?? NonFungibleAssetProvider.OPENSEA, + ) + + //#region asset order from sdk + const assetOrder = useAssetOrder(token?.provider ?? NonFungibleAssetProvider.OPENSEA, token) //#endregion //#region offers const [offerPage, setOfferPage] = useState(0) - const offers = useMemo(() => { - if (!asset.value?.orders) return [] - const _orders = asset.value?.orders?.filter((x) => x.side === OrderSide.Buy) - - return _orders.slice(offerPage * 10, (offerPage + 1) * 10) - }, [asset.value?.orders, offerPage]) + const offers = useOrders( + tabIndex === CollectibleTab.OFFER ? token?.contractAddress : undefined, + tabIndex === CollectibleTab.OFFER ? token?.tokenId : undefined, + OrderSide.Buy, + ) //#endregion //#region orders const [orderPage, setOrderPage] = useState(0) - const orders = useMemo(() => { - if (!asset.value?.orders) return [] - const _orders = asset.value?.orders?.filter((x) => x.side === OrderSide.Sell) - - return _orders.slice(orderPage * 10, (orderPage + 1) * 10) - }, [asset.value?.orders, orderPage]) + const orders = useOrders( + tabIndex === CollectibleTab.LISTING ? token?.contractAddress : undefined, + tabIndex === CollectibleTab.LISTING ? token?.tokenId : undefined, + OrderSide.Sell, + ) //#endregion //#region events const [eventPage, setEventPage] = useState(0) - const events = useEvents(provider, tabIndex === CollectibleTab.HISTORY ? token : undefined, eventPage, 10) - + const cursors = useRef([]) + const events = useHistory( + tabIndex === CollectibleTab.HISTORY ? token?.contractAddress : undefined, + tabIndex === CollectibleTab.HISTORY ? token?.tokenId : undefined, + ) //#endregion return { token, asset, - provider, + provider: token?.provider ?? NonFungibleAssetProvider.OPENSEA, assetOrder, diff --git a/packages/mask/src/plugins/Collectible/hooks/useEvents.ts b/packages/mask/src/plugins/Collectible/hooks/useEvents.ts deleted file mode 100644 index dd71730b5a9a..000000000000 --- a/packages/mask/src/plugins/Collectible/hooks/useEvents.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { ChainId, useChainId } from '@masknet/web3-shared-evm' -import { CollectibleToken, NFTHistory, CollectibleProvider, OpenSeaAssetEventType } from '../types' -import { PluginCollectibleRPC } from '../messages' -import { NullAddress, NullContractAddress, OpenSeaAccountURL } from '../constants' -import { toRaribleImage, toTokenIdentifier } from '../helpers' -import { resolveRaribleUserNetwork } from '../pipes' - -export function useEvents(provider: CollectibleProvider, token?: CollectibleToken, page?: number, size?: number) { - const chainId = useChainId() - return useAsyncRetry<{ data: NFTHistory[]; hasNextPage: boolean }>(async () => { - if (!token || (chainId !== ChainId.Mainnet && chainId !== ChainId.Ropsten)) - return { - data: [] as NFTHistory[], - hasNextPage: false, - } as UnboxPromise - switch (provider) { - case CollectibleProvider.OPENSEA: - const openseaEvents = await PluginCollectibleRPC.getEvents( - token.contractAddress, - token.tokenId, - page, - size, - ) - - return { - data: openseaEvents.map((event) => { - const accountPair = - event.event_type === OpenSeaAssetEventType.SUCCESSFUL - ? { - from: { - username: event.seller?.user?.username, - address: event.seller?.address, - imageUrl: event.seller?.profile_img_url, - link: `${OpenSeaAccountURL}${ - event.seller?.user?.username ?? event.seller?.address - }`, - }, - to: { - username: event.winner_account?.user?.username, - address: event.winner_account?.address, - imageUrl: event.winner_account?.profile_img_url, - link: `${OpenSeaAccountURL}${ - event.winner_account?.user?.username ?? event.winner_account?.address - }`, - }, - } - : { - from: { - username: event.from_account?.user?.username, - address: event.from_account?.address, - imageUrl: event.from_account?.profile_img_url, - link: `${OpenSeaAccountURL}${ - event.from_account?.user?.username ?? event.from_account?.address - }`, - }, - to: { - username: event.to_account?.user?.username, - address: event.to_account?.address, - imageUrl: event.to_account?.profile_img_url, - link: `${OpenSeaAccountURL}${ - event.to_account?.user?.username ?? event.to_account?.address - }`, - }, - } - return { - id: event.id, - accountPair, - price: { - quantity: event.quantity, - asset: event.asset, - paymentToken: event.payment_token, - price: event.bid_amount ?? event.ending_price ?? event.starting_price, - }, - eventType: event.event_type, - transactionBlockExplorerLink: event.transaction?.blockExplorerLink, - timestamp: new Date(`${event.created_date}Z`).getTime(), - } as NFTHistory - }), - hasNextPage: openseaEvents.length === size, - } - - case CollectibleProvider.RARIBLE: - const raribleEvents = await PluginCollectibleRPC.getHistoryFromRarible( - token.contractAddress, - token.tokenId, - ) - return { - data: raribleEvents.map((event) => { - return { - id: event.id, - accountPair: { - from: event.fromInfo - ? { - username: - event.fromInfo?.id === NullContractAddress - ? NullAddress - : event.fromInfo?.name, - address: event.fromInfo?.id, - imageUrl: toRaribleImage(event.fromInfo?.image), - link: `${resolveRaribleUserNetwork(chainId)}${event.fromInfo?.id ?? ''}`, - } - : null, - to: event.ownerInfo - ? { - username: event.ownerInfo?.name, - address: event.ownerInfo?.id, - imageUrl: toRaribleImage(event.ownerInfo?.image), - link: `${resolveRaribleUserNetwork(chainId)}${event.ownerInfo?.id ?? ''}`, - } - : null, - }, - eventType: event['@type'], - timestamp: new Date(event.date).getTime(), - price: event.price - ? { - quantity: String(event.price), - } - : null, - } as NFTHistory - }), - - hasNextPage: false, - } - } - }, [chainId, toTokenIdentifier(token), page, size, provider]) -} diff --git a/packages/mask/src/plugins/Collectible/pipes/index.ts b/packages/mask/src/plugins/Collectible/pipes/index.ts index e2213e108e5d..999ce985d8fc 100644 --- a/packages/mask/src/plugins/Collectible/pipes/index.ts +++ b/packages/mask/src/plugins/Collectible/pipes/index.ts @@ -1,8 +1,8 @@ import { identity } from 'lodash-unified' import { Network } from 'opensea-js' -import { ChainId, createLookupTableResolver } from '@masknet/web3-shared-evm' +import { ChainId, createLookupTableResolver, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { NullAddress, RaribleRopstenUserURL, RaribleUserURL } from '../constants' -import { CollectibleProvider, OpenSeaAssetEventType, RaribleEventType } from '../types' +import { OpenSeaAssetEventType, RaribleEventType } from '../types' import urlcat from 'urlcat' export function resolveOpenSeaAssetEventType(eventType: OpenSeaAssetEventType, fromUserName?: string) { @@ -44,10 +44,11 @@ export const resolveOpenSeaNetwork = createLookupTableResolver( +export const resolveCollectibleProviderName = createLookupTableResolver( { - [CollectibleProvider.OPENSEA]: 'OpenSea', - [CollectibleProvider.RARIBLE]: 'Rarible', + [NonFungibleAssetProvider.OPENSEA]: 'OpenSea', + [NonFungibleAssetProvider.RARIBLE]: 'Rarible', + [NonFungibleAssetProvider.NFTSCAN]: 'NFTScan', }, (providerType) => { throw new Error(`Unknown provider type: ${providerType}.`) diff --git a/packages/mask/src/plugins/Collectible/settings.ts b/packages/mask/src/plugins/Collectible/settings.ts index 1796a627ddc3..fb0468f81abd 100644 --- a/packages/mask/src/plugins/Collectible/settings.ts +++ b/packages/mask/src/plugins/Collectible/settings.ts @@ -1,10 +1,10 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm/types' import { createGlobalSettings } from '../../settings/createSettings' import { PLUGIN_IDENTIFIER } from './constants' -import { CollectibleProvider } from './types' -export const currentCollectibleProviderSettings = createGlobalSettings( +export const currentCollectibleProviderSettings = createGlobalSettings( `${PLUGIN_IDENTIFIER}+tradeProvider`, - CollectibleProvider.OPENSEA, + NonFungibleAssetProvider.OPENSEA, { primary: () => '', }, diff --git a/packages/mask/src/plugins/Collectible/types/index.ts b/packages/mask/src/plugins/Collectible/types/index.ts index 667b063ae8cf..ce43c7bbf643 100644 --- a/packages/mask/src/plugins/Collectible/types/index.ts +++ b/packages/mask/src/plugins/Collectible/types/index.ts @@ -1,7 +1,5 @@ import type { WyvernSchemaName } from 'opensea-js/lib/types' -import type { ChainId } from '@masknet/web3-shared-evm' -import type { OpenSeaAssetEventType, OpenSeaFungibleToken } from './opensea' -import type { RaribleEventType } from './rarible' +import type { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' export * from './opensea' export * from './rarible' @@ -10,6 +8,7 @@ export interface CollectibleJSON_Payload { chain_id: ChainId address: string token_id: string + provider: NonFungibleAssetProvider } export enum CollectibleTab { @@ -25,94 +24,5 @@ export interface CollectibleToken { tokenId: string contractAddress: string schemaName?: WyvernSchemaName -} - -export enum CollectibleProvider { - OPENSEA = 0, - RARIBLE = 1, -} - -export interface NFTAsset { - imageUrl: string - assetContract: { - name: string - description: string - schemaName: WyvernSchemaName - } - currentPrice?: number - owner?: { - address: string - profile_img_url?: string - user?: { - username: string - } - } - creator?: { - address: string - profile_img_url?: string - user?: { - username: string - } - } - traits?: { trait_type: string; value: string }[] - safelist_request_status: string - description: string - name?: string - animation_url?: string -} - -export interface NFTOrder { - unitPrice: number - usdPrice?: number - makerAccount: { - user?: { - username?: string - } - address?: string - profile_img_url: string - link: string - } - hash?: string - quantity?: number - expirationTime?: string - paymentTokenContract?: OpenSeaFungibleToken - paymentToken?: string -} - -export interface NFTHistory { - id: string - accountPair: { - from?: { - username?: string - address?: string - imageUrl?: string - link: string - } - to?: { - username?: string - address?: string - imageUrl?: string - link: string - } - } - price?: { - quantity: string - price: string - asset?: { - id: string - decimals: number - image_url: string - image_original_url: string - image_preview_url: string - asset_contract: { - symbol: string - } - permalink: string - } - paymentToken?: OpenSeaFungibleToken - } - - eventType: OpenSeaAssetEventType | RaribleEventType - transactionBlockExplorerLink?: string - timestamp: number + provider: NonFungibleAssetProvider } diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index 77d484e17d11..b29518afc9ab 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -6,7 +6,7 @@ import { raribleHostnames, rariblePathnameRegexMatcher, } from './constants' -import { ChainId, formatBalance } from '@masknet/web3-shared-evm' +import { ChainId, NonFungibleAssetProvider, formatBalance } from '@masknet/web3-shared-evm' import type { AssetEvent } from './types' export function checkUrl(url: string): boolean { @@ -35,6 +35,7 @@ export function getAssetInfoFromURL(url?: string) { chain_id: _url.host.includes('testnets') ? ChainId.Rinkeby : ChainId.Mainnet, address: openSeaMatched[1], token_id: openSeaMatched[2], + provider: NonFungibleAssetProvider.OPENSEA, } } //#endregion @@ -50,6 +51,7 @@ export function getAssetInfoFromURL(url?: string) { : ChainId.Mainnet, address: raribleMatched[1], token_id: raribleMatched[2], + provider: NonFungibleAssetProvider.RARIBLE, } } //#endregion diff --git a/packages/mask/src/plugins/EVM/base.ts b/packages/mask/src/plugins/EVM/base.ts index e1c2c6201f01..913d9508248f 100644 --- a/packages/mask/src/plugins/EVM/base.ts +++ b/packages/mask/src/plugins/EVM/base.ts @@ -1,8 +1,9 @@ import type { Plugin } from '@masknet/plugin-infra' -import { PLUGIN_ID, PLUGIN_ICON, PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_PROVIDERS, PLUGIN_NETWORKS } from './constants' +import { PLUGIN_DESCRIPTION, PLUGIN_ICON, PLUGIN_ID, PLUGIN_NAME, PLUGIN_NETWORKS, PLUGIN_PROVIDERS } from './constants' export const base: Plugin.Shared.Definition = { ID: PLUGIN_ID, + icon: PLUGIN_ICON, name: { fallback: PLUGIN_NAME }, description: { fallback: PLUGIN_DESCRIPTION }, diff --git a/packages/mask/src/plugins/EVM/hooks/index.ts b/packages/mask/src/plugins/EVM/hooks/index.ts index fa43c3ae4a4c..08e9caf6a481 100644 --- a/packages/mask/src/plugins/EVM/hooks/index.ts +++ b/packages/mask/src/plugins/EVM/hooks/index.ts @@ -1,3 +1,8 @@ +export * from './useAsset' +export * from './useNFTBalance' +export * from './useNFTs' +export * from './useHistory' +export * from './useOrders' export * from './useNetwork' export * from './useProvider' export * from './useInjectedProviderConnected' diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts new file mode 100644 index 000000000000..021ba4e5bfe8 --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -0,0 +1,30 @@ +import { useAsyncRetry } from 'react-use' +import { + NonFungibleAssetProvider, + currySameAddress, + isSameAddress, + useAccount, + useChainId, + useTokenConstants, +} from '@masknet/web3-shared-evm' +import { EVM_RPC } from '../messages' + +export function useAsset(address: string, token_id: string, provider: NonFungibleAssetProvider) { + const account = useAccount() + const chainId = useChainId() + const { WNATIVE_ADDRESS } = useTokenConstants() + + return useAsyncRetry(async () => { + const asset = await EVM_RPC.getAsset(address, token_id, chainId, provider) + + return { + ...asset, + is_order_weth: isSameAddress(asset?.desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS) ?? false, + is_collection_weth: asset?.collection?.payment_tokens?.some(currySameAddress(WNATIVE_ADDRESS)) ?? false, + is_owner: + asset?.top_ownerships.some((item: { owner: { address: string | undefined } }) => + isSameAddress(item.owner.address, account), + ) ?? false, + } + }, [account, chainId, WNATIVE_ADDRESS]) +} diff --git a/packages/mask/src/plugins/EVM/hooks/useHistory.ts b/packages/mask/src/plugins/EVM/hooks/useHistory.ts new file mode 100644 index 000000000000..11fa06b3b6a7 --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useHistory.ts @@ -0,0 +1,9 @@ +import { useAsyncRetry } from 'react-use' +import { EVM_RPC } from '../messages' + +export function useHistory(address?: string, tokenId?: string) { + return useAsyncRetry(async () => { + if (!address || !tokenId) return + return EVM_RPC.getHistory(address, tokenId) + }, [address, tokenId]) +} diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts new file mode 100644 index 000000000000..cf175b2cf8ad --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts @@ -0,0 +1,11 @@ +import { useAsyncRetry } from 'react-use' +import { useChainId } from '@masknet/web3-shared-evm' +import { EVM_RPC } from '../messages' + +export function useNFTBalance(address: string, contract_address?: string) { + const chainId = useChainId() + return useAsyncRetry(async () => { + if (!contract_address) return + return EVM_RPC.getNFTBalance(address, contract_address, chainId) + }, [address, contract_address, chainId]) +} diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTs.ts b/packages/mask/src/plugins/EVM/hooks/useNFTs.ts new file mode 100644 index 000000000000..3ee364aa9673 --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useNFTs.ts @@ -0,0 +1,9 @@ +import { useAsyncRetry } from 'react-use' +import { EVM_RPC } from '../messages' + +export function useNFTs(account: string, disabled = false) { + return useAsyncRetry(async () => { + if (!account || disabled) return null + return EVM_RPC.getNFTs(account) + }, [account, disabled]) +} diff --git a/packages/mask/src/plugins/EVM/hooks/useOrders.ts b/packages/mask/src/plugins/EVM/hooks/useOrders.ts new file mode 100644 index 000000000000..2674b335c302 --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useOrders.ts @@ -0,0 +1,12 @@ +import { OrderSide } from '@masknet/web3-providers' +import { useChainId } from '@masknet/web3-shared-evm' +import { useAsyncRetry } from 'react-use' +import { EVM_RPC } from '../messages' + +export function useOrders(address?: string, tokenId?: string, side?: OrderSide) { + const chainId = useChainId() + return useAsyncRetry(async () => { + if (!address || !tokenId) return + return EVM_RPC.getOrder(address, tokenId, side ?? OrderSide.Sell, chainId) + }, [address, tokenId, side, chainId]) +} diff --git a/packages/mask/src/plugins/EVM/index.ts b/packages/mask/src/plugins/EVM/index.ts index 48ab76cba202..3772d510c874 100644 --- a/packages/mask/src/plugins/EVM/index.ts +++ b/packages/mask/src/plugins/EVM/index.ts @@ -3,6 +3,7 @@ import { base } from './base' registerPlugin({ ...base, + SNSAdaptor: { load: () => import('./UI/SNSAdaptor'), hotModuleReload: (hot) => diff --git a/packages/mask/src/plugins/EVM/services.ts b/packages/mask/src/plugins/EVM/services.ts deleted file mode 100644 index 5914b704d67f..000000000000 --- a/packages/mask/src/plugins/EVM/services.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './apis' diff --git a/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts b/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts new file mode 100644 index 000000000000..45686464d024 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts @@ -0,0 +1,27 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' + +import { unreachable } from '@dimensiondev/kit' +import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' + +export async function getAsset( + address: string, + tokenId: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let asset + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + asset = await OpenSeaApi.getAsset(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.NFTSCAN: + asset = await NFTScanApi.getAsset(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.RARIBLE: + asset = await RaribleApi.getAsset(address, tokenId, chainId) + return asset + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts b/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts new file mode 100644 index 000000000000..f80f2114d86f --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts @@ -0,0 +1,23 @@ +import { OpenSeaApi } from '@masknet/web3-providers' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' +import { unreachable } from '@dimensiondev/kit' + +export async function getCollections( + address: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, + page?: number, + size?: number, +) { + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getCollections(address, { chainId, page, size }) + case NonFungibleAssetProvider.RARIBLE: + return [] + case NonFungibleAssetProvider.NFTSCAN: + return [] + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts b/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts new file mode 100644 index 000000000000..dabfba2f579e --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts @@ -0,0 +1,27 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' + +import { unreachable } from '@dimensiondev/kit' +import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' + +export async function getHistory( + address: string, + tokenId: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let asset + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + asset = await OpenSeaApi.getHistory(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.NFTSCAN: + asset = await NFTScanApi.getHistory(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.RARIBLE: + asset = await RaribleApi.getHistory(address, tokenId) + return asset + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getListings.ts b/packages/mask/src/plugins/EVM/services/Asset/getListings.ts new file mode 100644 index 000000000000..df8349af631f --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getListings.ts @@ -0,0 +1,27 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' + +import { unreachable } from '@dimensiondev/kit' + +export async function getListings( + address: string, + tokenId: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let asset + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + asset = await OpenSeaApi.getListings(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.NFTSCAN: + asset = await NFTScanApi.getListings(address, tokenId, chainId) + return asset + case NonFungibleAssetProvider.RARIBLE: + asset = await RaribleApi.getListings(address, tokenId, chainId) + return asset + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts b/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts new file mode 100644 index 000000000000..3267ff1e7630 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts @@ -0,0 +1,29 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' + +import { unreachable } from '@dimensiondev/kit' +import type { OrderSide } from '@masknet/web3-providers' + +export async function getOrder( + address: string, + tokenId: string, + side: OrderSide, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let asset + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + asset = await OpenSeaApi.getOrder(address, tokenId, side, chainId) + return asset + case NonFungibleAssetProvider.NFTSCAN: + asset = await NFTScanApi.getOrder(address, tokenId, side, chainId) + return asset + case NonFungibleAssetProvider.RARIBLE: + asset = await RaribleApi.getOrder(address, tokenId, side, chainId) + return asset + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/index.ts b/packages/mask/src/plugins/EVM/services/Asset/index.ts new file mode 100644 index 000000000000..965a778b4262 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/index.ts @@ -0,0 +1,5 @@ +export * from './getAsset' +export * from './getHistory' +export * from './getListings' +export * from './getOrder' +export * from './getCollections' diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts new file mode 100644 index 000000000000..6200d30f3a21 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts @@ -0,0 +1,26 @@ +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { unreachable } from '@dimensiondev/kit' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' + +export async function getNFT( + address: string, + tokenId: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let token + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + token = await OpenSeaApi.getNFT(address, tokenId, chainId) + return token + case NonFungibleAssetProvider.NFTSCAN: + token = await NFTScanApi.getNFT(address, tokenId) + return token + case NonFungibleAssetProvider.RARIBLE: + token = await RaribleApi.getNFT(address, tokenId) + return token + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts new file mode 100644 index 000000000000..599cd307c271 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts @@ -0,0 +1,26 @@ +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { unreachable } from '@dimensiondev/kit' +import { currentChainIdSettings } from '../../../Wallet/settings' + +export async function getNFTBalance( + address: string, + contract_address: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let balance + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + balance = await OpenSeaApi.getContractBalance(address, contract_address, chainId) + return balance + case NonFungibleAssetProvider.NFTSCAN: + balance = await NFTScanApi.getContractBalance(address, contract_address, chainId) + return balance + case NonFungibleAssetProvider.RARIBLE: + balance = await RaribleApi.getContractBalance(address, contract_address, chainId) + return balance + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts new file mode 100644 index 000000000000..b926f439e2e7 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts @@ -0,0 +1,21 @@ +import { unreachable } from '@dimensiondev/kit' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { OpenSeaApi } from '@masknet/web3-providers' +import { currentChainIdSettings } from '../../../Wallet/settings' + +export async function getNFTContract( + contractAddress: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + case NonFungibleAssetProvider.NFTSCAN: + const assetContract = await OpenSeaApi.getContract(contractAddress, chainId) + return assetContract + case NonFungibleAssetProvider.RARIBLE: + return + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts new file mode 100644 index 000000000000..d483fd4de356 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts @@ -0,0 +1,25 @@ +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { unreachable } from '@dimensiondev/kit' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' + +export async function getNFTs( + from: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, +) { + let tokens + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + tokens = await OpenSeaApi.getNFTs(from, chainId) + return tokens + case NonFungibleAssetProvider.NFTSCAN: + tokens = await NFTScanApi.getNFTs(from) + return tokens + case NonFungibleAssetProvider.RARIBLE: + tokens = await RaribleApi.getNFTs(from, chainId) + return tokens + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts new file mode 100644 index 000000000000..affef1f8d086 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts @@ -0,0 +1,28 @@ +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' + +import { unreachable } from '@dimensiondev/kit' +import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { currentChainIdSettings } from '../../../Wallet/settings' + +export async function getNFTsPaged( + from: string, + chainId = currentChainIdSettings.value, + provider = NonFungibleAssetProvider.OPENSEA, + page?: number, + size?: number, +) { + let assets + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + assets = await OpenSeaApi.getNFTsPaged(from, { chainId, page, size }) + return assets + case NonFungibleAssetProvider.NFTSCAN: + assets = await NFTScanApi.getNFTsPaged(from, { chainId, page, size }) + return assets + case NonFungibleAssetProvider.RARIBLE: + assets = await RaribleApi.getNFTsPaged(from, { chainId, page, size }) + return assets + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/index.ts b/packages/mask/src/plugins/EVM/services/NFT/index.ts new file mode 100644 index 000000000000..2a004d34a656 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/NFT/index.ts @@ -0,0 +1,5 @@ +export * from './getNFTBalance' +export * from './getNFT' +export * from './getNFTs' +export * from './getNFTContract' +export * from './getNFTsPaged' diff --git a/packages/mask/src/plugins/EVM/services/index.ts b/packages/mask/src/plugins/EVM/services/index.ts new file mode 100644 index 000000000000..01ad64779b05 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/index.ts @@ -0,0 +1,2 @@ +export * from './Asset' +export * from './NFT' diff --git a/packages/mask/src/plugins/EVM/apis/index.ts b/packages/mask/src/plugins/EVM/types/index.ts similarity index 100% rename from packages/mask/src/plugins/EVM/apis/index.ts rename to packages/mask/src/plugins/EVM/types/index.ts diff --git a/packages/mask/src/plugins/EVM/utils/index.ts b/packages/mask/src/plugins/EVM/utils/index.ts new file mode 100644 index 000000000000..336ce12bb910 --- /dev/null +++ b/packages/mask/src/plugins/EVM/utils/index.ts @@ -0,0 +1 @@ +export {} diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx index 777f45c8271b..12f389e788da 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx @@ -20,8 +20,8 @@ import OpenInNewIcon from '@mui/icons-material/OpenInNew' import Fuse from 'fuse.js' import { useERC721ContractDetailed } from '@masknet/web3-shared-evm' import classNames from 'classnames' -import { useNFTscanFindAssets } from '../hooks/useNFTscanFindAssets' import { unionBy } from 'lodash-unified' +import { useNFTs } from '../../EVM/hooks/useNFTs' const useStyles = makeStyles()((theme) => ({ search: { @@ -151,7 +151,7 @@ export function SelectNftContractDialog(props: SelectNftContractDialogProps) { }, [id, setDialog]) //#endregion - const { value: assets } = useNFTscanFindAssets(account, !open) + const { value: assets } = useNFTs(account, !open) const erc721InDb = useERC721Tokens() const allContractsInDb = unionBy( diff --git a/packages/mask/src/plugins/Wallet/hooks/useNFTscanFindAssets.ts b/packages/mask/src/plugins/Wallet/hooks/useNFTscanFindAssets.ts deleted file mode 100644 index ddcbc1eefd5e..000000000000 --- a/packages/mask/src/plugins/Wallet/hooks/useNFTscanFindAssets.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { WalletRPC } from '../messages' -import { useAsyncRetry } from 'react-use' - -export function useNFTscanFindAssets(account: string, disabled = false) { - return useAsyncRetry(async () => { - if (!account || disabled) return null - return WalletRPC.nftscanFindAssets(account) - }, [account, disabled]) -} diff --git a/packages/mask/src/plugins/Wallet/services/assets.ts b/packages/mask/src/plugins/Wallet/services/assets.ts index 5a1d53150a74..4f168aa161b9 100644 --- a/packages/mask/src/plugins/Wallet/services/assets.ts +++ b/packages/mask/src/plugins/Wallet/services/assets.ts @@ -4,7 +4,6 @@ import { ChainId, NonFungibleAssetProvider, createERC20Token, - createERC721Token, createNativeToken, CurrencyType, EthereumTokenType, @@ -23,8 +22,8 @@ import { import BigNumber from 'bignumber.js' import { values } from 'lodash-unified' import { EthereumAddress } from 'wallet.ts' +import { EVM_RPC } from '../../EVM/messages' import * as DebankAPI from '../apis/debank' -import * as OpenSeaAPI from '../apis/opensea' import * as ZerionAPI from '../apis/zerion' import { resolveChainByScope, resolveZerionAssetsScopeName } from '../pipes' import type { @@ -44,14 +43,10 @@ export async function getCollectionsNFT( size?: number, ): Promise<{ collections: ERC721TokenCollectionInfo[]; hasNextPage: boolean }> { if (provider === NonFungibleAssetProvider.OPENSEA) { - const { collections } = await OpenSeaAPI.getCollections(address, { chainId, page, size }) + const collections = await EVM_RPC.getCollections(address, chainId, provider, page, size) return { - collections: collections.map((x) => ({ - name: x.name, - image: x.image_url || undefined, - slug: x.slug, - })), + collections, hasNextPage: collections.length === size, } } @@ -70,41 +65,20 @@ export async function getAssetsListNFT( size?: number, collection?: string, ): Promise<{ assets: ERC721TokenDetailed[]; hasNextPage: boolean }> { - if (provider === NonFungibleAssetProvider.OPENSEA) { - const { assets } = await OpenSeaAPI.getAssetsList(address, { chainId, page, size, collection }) + if (!EthereumAddress.isValid(address)) return { - assets: assets - .filter( - (x) => - ['non-fungible', 'semi-fungible'].includes(x.asset_contract.asset_contract_type) || - ['ERC721', 'ERC1155'].includes(x.asset_contract.schema_name), - ) - .map((x) => - createERC721Token( - { - chainId: ChainId.Mainnet, - type: EthereumTokenType.ERC721, - name: x.asset_contract.name, - symbol: x.asset_contract.symbol, - address: x.asset_contract.address, - }, - { - name: x.name || x.asset_contract.name, - description: x.description || x.asset_contract.symbol, - image: x.image_url || x.image_preview_url || x.asset_contract.image_url || '', - }, - x.token_id, - ), - ), - hasNextPage: assets.length === size, + assets: [], + hasNextPage: false, } - } + const tokens = await EVM_RPC.getNFTsPaged(address, chainId, provider, page, size) return { - assets: [], - hasNextPage: false, + assets: tokens, + hasNextPage: tokens.length === size, } } +const filterAssetType = ['compound', 'trash', 'uniswap', 'uniswap-v2', 'nft'] + export async function getAssetsList( address: string, provider: FungibleAssetProvider, @@ -187,8 +161,6 @@ function formatAssetsFromDebank(data: BalanceRecord[], network?: NetworkType) { }) } -const filterAssetType = ['compound', 'trash', 'uniswap', 'uniswap-v2', 'nft'] - function formatAssetsFromZerion( data: ZerionAddressAsset[] | ZerionAddressCovalentAsset[], scope: SocketRequestAssetScope, diff --git a/packages/mask/src/plugins/Wallet/services/index.ts b/packages/mask/src/plugins/Wallet/services/index.ts index 3cb402518967..b24f93f2bd2c 100644 --- a/packages/mask/src/plugins/Wallet/services/index.ts +++ b/packages/mask/src/plugins/Wallet/services/index.ts @@ -11,6 +11,5 @@ export * from './domain' export * from './cloud' export * from './estimateGasFee' export * from './tokenPrice' -export * from './nftscan' -export * from './wallet' +export * from './wallet/index' export * from './legacyWallet' diff --git a/packages/mask/src/tsconfig.json b/packages/mask/src/tsconfig.json index 54e75d0b9e6c..28176677c278 100644 --- a/packages/mask/src/tsconfig.json +++ b/packages/mask/src/tsconfig.json @@ -18,6 +18,7 @@ { "path": "../../shared-base" }, { "path": "../../web3-shared/evm" }, { "path": "../../web3-shared/flow" }, + { "path": "../../web3-providers" }, { "path": "../../theme" }, { "path": "../../icons" }, { "path": "../../plugin-infra" }, diff --git a/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx b/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx index 4d91cf5c1bd2..e2feb84e1ed8 100644 --- a/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx +++ b/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react' import { v4 as uuid } from 'uuid' -import { ERC721ContractDetailed, useERC721ContractBalance, useAccount, isSameAddress } from '@masknet/web3-shared-evm' +import { ERC721ContractDetailed, useERC721ContractBalance, useAccount } from '@masknet/web3-shared-evm' import classNames from 'classnames' import { EthereumAddress } from 'wallet.ts' import { Box, Typography, CircularProgress } from '@mui/material' @@ -9,7 +9,7 @@ import { useRemoteControlledDialog } from '@masknet/shared' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { SelectNftContractDialogEvent, WalletMessages } from '../../plugins/Wallet/messages' import { useI18N } from '../../utils' -import { useNFTscanFindAssets } from '../../plugins/Wallet/hooks/useNFTscanFindAssets' +import { useNFTBalance } from '../../plugins/EVM/hooks/useNFTBalance' interface StyleProps { hasIcon: boolean @@ -72,19 +72,15 @@ export function ERC721ContractSelectPanel(props: ERC721TokenSelectPanelProps) { const account = useAccount() const { classes } = useStyles({ hasIcon: Boolean(contract?.iconURL) }) const { value: balanceFromChain, loading: loadingFromChain } = useERC721ContractBalance(contract?.address, account) - const { value: assets, loading: loadingFromNFTscan } = useNFTscanFindAssets(account, !contract) + const { value: balanceFromNFTscan, loading: loadingBalanceFromNFTscan } = useNFTBalance(account, contract?.address) const { t } = useI18N() - const balanceFromNFTscan = assets - ? assets.find((asset) => isSameAddress(asset.contractDetailed.address, contract?.address))?.balance - : undefined - const balance = balanceFromChain ? Number(balanceFromChain) : balanceFromNFTscan ?? 0 onBalanceChange(balance) - const loading = (loadingFromChain || loadingFromNFTscan) && !balance + const loading = (loadingFromChain || loadingBalanceFromNFTscan) && !balance //#region select contract const [id] = useState(uuid()) diff --git a/packages/web3-providers/NFTScan/constants.ts b/packages/web3-providers/NFTScan/constants.ts new file mode 100644 index 000000000000..e0297e9e78de --- /dev/null +++ b/packages/web3-providers/NFTScan/constants.ts @@ -0,0 +1,4 @@ +export const NFTSCAN_ID = 't9k2o5GC' +export const NFTSCAN_SECRET = '21da1d638ef5d0bf76e37aa5c2da7fd789ade9e3' +export const NFTSCAN_URL = 'https://restapi.nftscan.com' +export const NFTSCAN_BASE_API = `${NFTSCAN_URL}/api/v1` diff --git a/packages/web3-providers/NFTScan/index.ts b/packages/web3-providers/NFTScan/index.ts new file mode 100644 index 000000000000..f780f8235dd8 --- /dev/null +++ b/packages/web3-providers/NFTScan/index.ts @@ -0,0 +1,173 @@ +import { NFTSCAN_BASE_API, NFTSCAN_ID, NFTSCAN_SECRET, NFTSCAN_URL } from './constants' +import type { NFTScanAsset, NFT_Assets } from './types' +import { + createERC721ContractDetailed, + ChainId, + createERC721Token, + ERC721ContractDetailed, + EthereumTokenType, + ERC721TokenDetailed, + isSameAddress, +} from '@masknet/web3-shared-evm' +import type { NFTAsset, OrderSide } from '../types' + +let token: string = '' +let token_expiration: number = 0 + +async function getToken() { + const params = new URLSearchParams() + params.append('apiKey', NFTSCAN_ID) + params.append('apiSecret', NFTSCAN_SECRET) + const response = await fetch(`${NFTSCAN_URL}/gw/token?${params.toString()}`, { + mode: 'cors', + }) + + const { data }: { data: { accessToken: string; expiration: number } } = await response.json() + token = data.accessToken + token_expiration = Date.now() + data.expiration * 1000 +} + +async function fetchAsset(path: string, config = {} as RequestInit) { + if (token === '' || Date.now() > token_expiration) { + await getToken() + } + const response = await fetch(`${NFTSCAN_BASE_API}/${path}`, { + method: 'POST', + ...config, + headers: { + 'Access-Token': token, + }, + }) + + if (!response.ok) return + + return response.json() +} + +function createERC721ContractDetailedFromAssetContract(asset: NFTScanAsset) { + return createERC721ContractDetailed( + ChainId.Mainnet, + asset.trade_contract, + 'unknown name', + asset.trade_symbol ?? 'unknown symbol', + ) +} + +function createERC721TokenAsset(asset: NFTScanAsset) { + const json = JSON.parse(asset.nft_json) + return createERC721Token( + createERC721ContractDetailedFromAssetContract(asset), + { + name: json.name ?? 'unknown name', + description: json.description ?? 'unknown symbol', + image: json.image || '', + owner: asset.nft_holder ?? '', + }, + asset.token_id, + ) +} + +async function getContractsAndBalance(address: string) { + const response = await fetchAsset('getGroupByNftContract', { + body: JSON.stringify({ + erc: 'erc721', + user_address: address, + }), + }) + if (!response) return null + + const { data }: { data: NFT_Assets[] } = response + + return data + .map((x) => { + const contractDetailed: ERC721ContractDetailed = { + name: x.nft_platform_name, + symbol: '', + address: x.nft_contract_address, + chainId: ChainId.Mainnet, + type: EthereumTokenType.ERC721, + } + const balance = x.nft_asset.length + + return { + contractDetailed, + balance, + } + }) + .sort((a, b) => a.balance - b.balance) +} + +export async function getNFT(address: string, tokenId: string, chainId = ChainId.Mainnet) { + const response = await fetchAsset('getSingleNft', { + body: JSON.stringify({ + nft_address: address, + token_id: tokenId, + }), + }) + if (!response) return null + + const { data }: { data: NFTScanAsset } = response + + return createERC721TokenAsset(data) +} + +export async function getNFTs(from: string, chainId = ChainId.Mainnet) { + let tokens: ERC721TokenDetailed[] = [] + let page = 0 + let assets + const size = 50 + do { + assets = await getNFTsPaged(from, { chainId, page, size }) + if (!assets) return [] + tokens = tokens.concat(assets) + page = page + 1 + } while (assets.length === size) + + return tokens +} + +export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { + const { size = 50, page = 0 } = opts + + const response = await fetchAsset('getAllNftByUserAddress', { + body: JSON.stringify({ + page_size: size, + page_index: page, + use_address: from, + erc: 'erc721', + }), + }) + if (!response) return [] + + const { data }: { data: { content: NFTScanAsset[]; page_index: number; page_size: number; total: number } } = + response + + return data.content.map((asset) => createERC721TokenAsset(asset)) +} + +export async function getContractBalance(address: string, contract_address: string, chainId: ChainId) { + const response = await getContractsAndBalance(address) + if (!response) return + + return response.find((x) => isSameAddress(x.contractDetailed.address, contract_address))?.balance +} + +export async function getAsset(address: string, tokenId: string, chainId: ChainId) { + return {} as NFTAsset +} + +export async function getHistory(address: string, tokenId: string, chainId: ChainId) { + return [] +} + +export async function getListings(address: string, tokenId: string, chainId: ChainId) { + return [] +} + +export async function getOrder(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { + return [] +} + +export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { + return [] +} diff --git a/packages/web3-providers/NFTScan/types.ts b/packages/web3-providers/NFTScan/types.ts new file mode 100644 index 000000000000..af287e07ce59 --- /dev/null +++ b/packages/web3-providers/NFTScan/types.ts @@ -0,0 +1,28 @@ +export interface NFTScanAsset { + last_price: string + nft_asset_id: string + nft_content_uri: string + nft_cover: string + nft_create_hash: string + nft_create_time: string + nft_creator: string + nft_detail: string + nft_holder: string + nft_json: string + nft_value: string + token_id: string + + trade_contract: string + trade_symbol: string + trade_decimal: string +} + +export type NFT_Assets = { + nft_asset: NFTScanAsset[] + nft_asset_count: number + nft_contract_address: string + nft_platform_count: number + nft_platform_describe: string + nft_platform_image: string + nft_platform_name: string +} diff --git a/packages/web3-providers/index.ts b/packages/web3-providers/index.ts new file mode 100644 index 000000000000..183c5641378d --- /dev/null +++ b/packages/web3-providers/index.ts @@ -0,0 +1,5 @@ +export * as OpenSeaApi from './opensea' +export * as RaribleApi from './rarible' +export * as NFTScanApi from './NFTScan' +export * from './types' +export * from './utils' diff --git a/packages/web3-providers/opensea/constants.ts b/packages/web3-providers/opensea/constants.ts new file mode 100644 index 000000000000..58ee01865bf1 --- /dev/null +++ b/packages/web3-providers/opensea/constants.ts @@ -0,0 +1,4 @@ +export const OpenSeaAccountURL = 'https://opensea.io/accounts/' +export const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' +//export const OpenSea_API_URL = 'https://opensea-proxy.r2d2.to' +export const OpenSea_API_URL = 'https://api.opensea.io' diff --git a/packages/web3-providers/opensea/index.ts b/packages/web3-providers/opensea/index.ts index 336ce12bb910..a20094fbaed0 100644 --- a/packages/web3-providers/opensea/index.ts +++ b/packages/web3-providers/opensea/index.ts @@ -1 +1,313 @@ -export {} +import { OpenSeaAccountURL, OPENSEA_API_KEY, OpenSea_API_URL } from './constants' +import type { OpenSeaAssetContract, OpenSeaAssetEvent, OpenSeaResponse, OpenSeaCollection } from './types' +import urlcat from 'urlcat' +import { head, uniqBy } from 'lodash-unified' +import type { AssetCollection, AssetOrder, NFTAsset, NFTHistory, OrderSide } from '../types' +import { getOrderUnitPrice, getOrderUSDPrice, toDate, toTokenDetailed } from '../utils' + +import BigNumber from 'bignumber.js' +import { + ChainId, + createERC721ContractDetailed, + createERC721Token, + ERC721TokenDetailed, + EthereumTokenType, + isSameAddress, +} from '@masknet/web3-shared-evm' +async function fetchAsset(url: string, chainId: ChainId) { + if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return + + const response = await ( + await fetch(url, { + method: 'GET', + mode: 'cors', + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, + }) + ).json() + + return response +} + +export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { + const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts + const params = new URLSearchParams() + params.append('owner', from.toLowerCase()) + params.append('limit', String(size)) + params.append('offset', String(size * page)) + + const asset = await fetchAsset(`${OpenSea_API_URL}/api/v1/assets?${params.toString()}`, chainId) + if (!asset) return [] + + return asset.assets.map((asset: OpenSeaResponse) => createERC721TokenAsset(from, asset.token_id, chainId, asset)) +} + +function createERC721ContractDetailedFromAssetContract( + address: string, + chainId: ChainId, + assetContract?: OpenSeaAssetContract, +) { + return createERC721ContractDetailed(chainId, address, assetContract?.name ?? 'unknown name', '') +} + +function createERC721TokenAsset(address: string, tokenId: string, chainId: ChainId, asset?: OpenSeaResponse) { + return createERC721Token( + createERC721ContractDetailed(chainId, address, asset?.name ?? 'unknown name', ''), + { + name: asset?.name ?? asset?.asset_contract.name ?? 'unknown name', + description: asset?.description ?? '', + image: asset?.image_url_original ?? asset?.image_url ?? asset?.image_preview_url ?? '', + owner: asset?.owner.address ?? '', + }, + tokenId, + ) +} + +async function _getAsset(address: string, tokenId: string, chainId: ChainId) { + const asset = (await fetchAsset( + urlcat(`${OpenSea_API_URL}/api/v1/asset/:address/:tokenId`, { address, tokenId }), + chainId, + )) as OpenSeaResponse + return asset +} + +export async function getContract(address: string, chainId = ChainId.Mainnet) { + const assetContract = await fetchAsset(`${OpenSea_API_URL}/api/v1/asset_contract/${address}`, chainId) + + return createERC721ContractDetailedFromAssetContract(address, chainId, assetContract) +} + +export async function getNFT(address: string, tokenId: string, chainId: ChainId) { + const asset = await _getAsset(address, tokenId, chainId) + return createERC721TokenAsset(address, tokenId, chainId, asset) +} + +export async function getNFTs(from: string, chainId: ChainId) { + let tokens: ERC721TokenDetailed[] = [] + let page = 0 + let assets + const size = 50 + do { + assets = await getNFTsPaged(from, { chainId, page, size }) + tokens = tokens.concat(assets) + page = page + 1 + } while (assets.length === size) + + return tokens +} + +export async function getContractBalance(address: string, contract_address: string, chainId: ChainId) { + const assets = await getNFTs(address, chainId) + + return assets.filter((x) => isSameAddress(x.contractDetailed.address, contract_address)).length +} + +function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { + const desktopOrder = head( + asset.sell_orders?.sort((a, b) => + new BigNumber(getOrderUSDPrice(a.current_price, a.payment_token_contract?.usd_price) ?? 0) + .minus(getOrderUSDPrice(a.current_price, a.payment_token_contract?.usd_price) ?? 0) + .toNumber(), + ), + ) + + return { + is_verified: ['approved', 'verified'].includes(asset.collection?.safelist_request_status ?? ''), + // it's an IOS string as my inspection + is_auction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), + image_url: asset.image_url_original ?? asset.image_url ?? asset.image_preview_url ?? '', + asset_contract: { + name: asset.asset_contract.name, + description: asset.asset_contract.description, + schemaName: asset.asset_contract.schema_name, + }, + current_price: desktopOrder + ? new BigNumber( + getOrderUnitPrice( + desktopOrder.current_price, + desktopOrder.payment_token_contract?.decimals, + desktopOrder.quantity, + ) ?? 0, + ).toNumber() + : null, + current_symbol: desktopOrder?.payment_token_contract?.symbol ?? 'ETH', + owner: { + ...asset.owner, + link: `${OpenSeaAccountURL}${asset.owner?.user?.username ?? asset.owner.address ?? ''}`, + }, + creator: { + ...asset.creator, + link: `${OpenSeaAccountURL}${asset.creator?.user?.username ?? asset.creator?.address ?? ''}`, + }, + token_id: asset.token_id, + token_address: asset.token_address, + traits: asset.traits, + safelist_request_status: asset.collection?.safelist_request_status ?? '', + description: asset.description, + name: asset.name ?? asset.collection.name, + collection_name: asset.collection.name, + animation_url: asset.animation_url, + end_time: asset.endTime + ? new Date(asset.endTime) + : desktopOrder + ? toDate(Number.parseInt(desktopOrder.listing_time as unknown as string, 10)) + : null, + order_payment_tokens: desktopOrder?.payment_token_contract + ? [toTokenDetailed(chainId, desktopOrder.payment_token_contract)] + : [], + offer_payment_tokens: uniqBy( + asset.collection.payment_tokens.map((x) => toTokenDetailed(chainId, x)), + (x) => x.address.toLowerCase(), + ).filter((x) => x.type === EthereumTokenType.ERC20), + slug: asset.collection.slug, + desktopOrder, + top_ownerships: asset.top_ownerships.map((x) => ({ owner: x.owner })), + collection: asset.collection as unknown as AssetCollection, + response_: asset as any, + } as NFTAsset +} + +export async function getAsset(address: string, tokenId: string, chainId: ChainId) { + const asset = await _getAsset(address, tokenId, chainId) + if (!asset) return + + return createNFTAsset(asset, chainId) +} + +export async function getHistory(address: string, tokenId: string, chainId: ChainId) { + const params = new URLSearchParams() + params.append('asset_contract_address', address) + params.append('token_id', tokenId) + params.append('offset', '0') + params.append('limit', '100') + + const fetchResponse = await ( + await fetch(`${OpenSea_API_URL}/api/v1/events?${params.toString()}`, { + mode: 'cors', + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, + }) + ).json() + + const { asset_events }: { asset_events: OpenSeaAssetEvent[] } = fetchResponse + + return asset_events.map((event) => createNFTHistory(event)) +} + +function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { + const accountPair = + event.event_type === 'successful' + ? { + from: { + username: event.seller?.user?.username, + address: event.seller?.address, + imageUrl: event.seller?.profile_img_url, + link: `${OpenSeaAccountURL}${event.seller?.user?.username ?? event.seller?.address}`, + }, + to: { + username: event.winner_account?.user?.username, + address: event.winner_account?.address, + imageUrl: event.winner_account?.profile_img_url, + link: `${OpenSeaAccountURL}${ + event.winner_account?.user?.username ?? event.winner_account?.address + }`, + }, + } + : { + from: { + username: event.from_account?.user?.username, + address: event.from_account?.address, + imageUrl: event.from_account?.profile_img_url, + link: `${OpenSeaAccountURL}${event.from_account?.user?.username ?? event.from_account?.address}`, + }, + to: { + username: event.to_account?.user?.username, + address: event.to_account?.address, + imageUrl: event.to_account?.profile_img_url, + link: `${OpenSeaAccountURL}${event.to_account?.user?.username ?? event.to_account?.address}`, + }, + } + return { + id: event.id, + accountPair, + price: { + quantity: event.quantity, + asset: event.asset, + paymentToken: event.payment_token, + price: event.bid_amount ?? event.ending_price ?? event.starting_price, + }, + eventType: event.event_type, + transactionBlockExplorerLink: event.transaction?.blockExplorerLink, + timestamp: new Date(`${event.created_date}Z`).getTime(), + } as NFTHistory +} + +export async function getListings(address: string, tokenId: string, chainId: ChainId) { + return [] +} + +async function fetchOrder( + address: string, + tokenId: string, + side: number, + page: number, + size: number, + chainId: ChainId, +) { + const params = new URLSearchParams() + params.append('asset_contract_address', address) + params.append('token_id', tokenId) + params.append('side', side.toString()) + params.append('offset', page.toString()) + params.append('limit', size.toString()) + const response = await ( + await fetch(`${OpenSea_API_URL}/wyvern/v1/orders?${params.toString()}`, { + method: 'GET', + mode: 'cors', + headers: { + Accept: 'application/json', + 'x-api-key': OPENSEA_API_KEY, + }, + }) + ).json() + + const { orders }: { orders: AssetOrder[] } = response + return orders +} + +export async function getOrder(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { + let orders: AssetOrder[] = [] + + let page = 0 + const size = 50 + let order + do { + order = await fetchOrder(address, tokenId, side, page, size, chainId) + orders = orders.concat(order ?? []) + page = page + 1 + } while (order.length === size) + + return orders +} + +export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { + const params = new URLSearchParams() + params.append('offset', `${opts.page ?? '0'}`) + params.append('limit', `${opts.size ?? '50'}`) + + const { collections }: { collections: OpenSeaCollection[] } = await ( + await fetch(`${OpenSea_API_URL}/api/v1/collections?${params.toString()}`, { + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, + }) + ).json() + + return collections.map((x) => ({ + name: x.name, + image: x.image_url || undefined, + slug: x.slug, + })) +} diff --git a/packages/web3-providers/opensea/types.ts b/packages/web3-providers/opensea/types.ts new file mode 100644 index 000000000000..aa9895f5a13e --- /dev/null +++ b/packages/web3-providers/opensea/types.ts @@ -0,0 +1,176 @@ +import type { AssetOrder } from '../types' +export interface OpenSeaFees { + opensea_seller_fee_basis_points: number + opensea_buyer_fee_basis_points: number + dev_seller_fee_basis_points: number + dev_buyer_fee_basis_points: number +} + +export interface Asset { + token_id: string + token_address: string + schema_name?: string + version?: string + name?: string + decimals?: number +} + +export interface OpenSeaAssetContract extends OpenSeaFees { + name: string + address: string + type: string + schema_name: string + seller_fee_basis_points: number + buyer_fee_basis_points: number + description: string + token_symbol: string + image_url: string + stats?: object + traits?: object[] + external_link?: string + wiki_link?: string +} + +interface NumericalTraitStats { + min: number + max: number +} +interface StringTraitStats { + [key: string]: number +} + +interface OpenSeaTraitStats { + [traitName: string]: NumericalTraitStats | StringTraitStats +} + +interface OpenSeaFungibleToken { + image_url?: string + eth_price?: string + usd_price?: string + name: string + symbol: string + decimals: number + address: string +} + +interface OpenSeaCustomAccount { + address: string + profile_img_url: string + user?: { + username: string + } +} + +export interface OpenSeaCollection extends OpenSeaFees { + name: string + slug: string + editors: string[] + hidden: boolean + featured: boolean + created_date: string + description: string + image_url: string + largeImage_url: string + featured_image_url: string + stats: object + display_data: object + payment_tokens: OpenSeaFungibleToken[] + payout_address?: string + trait_stats: OpenSeaTraitStats + external_link?: string + wiki_link?: string + safelist_request_status: string +} + +export interface OpenSeaResponse extends Asset { + animation_url: string + asset_contract: OpenSeaAssetContract + collection: OpenSeaCollection + name: string + description: string + owner: OpenSeaCustomAccount + orders: AssetOrder[] | null + buy_orders: AssetOrder[] | null + sell_orders: AssetOrder[] | null + is_presale: boolean + image_url: string + image_preview_url: string + image_url_original: string + image_url_thumbnail: string + opensea_link: string + external_link: string + traits: { + trait_type: string + value: string + }[] + num_sales: number + last_sale: AssetEvent | null + background_color: string | null + transfer_fee: string | null + transfer_fee_payment_token: OpenSeaFungibleToken | null + top_ownerships: { + owner: OpenSeaCustomAccount + quantity: string + }[] + creator: OpenSeaCustomAccount + endTime: string +} + +interface AssetEvent { + event_type: string + event_timestamp: number + auction_type: string + total_price: string + transaction: Transaction | null + payment_token: OpenSeaFungibleToken | null +} + +interface Transaction { + from_account: OpenSeaCustomAccount + to_account: OpenSeaCustomAccount + created_date: string + modified_date: string + transaction_hash: string + transaction_index: string + block_number: string + block_hash: string + timestamp: number +} + +export interface OpenSeaAssetEvent { + id: string + event_type: string + from_account?: OpenSeaCustomAccount + to_account?: OpenSeaCustomAccount + seller?: OpenSeaCustomAccount + winner_account?: OpenSeaCustomAccount + asset: { + id: string + decimals: number + image_url: string + image_original_url: string + image_preview_url: string + asset_contract: { + symbol: string + } + permalink: string + } + payment_token: OpenSeaFungibleToken + quantity: string + ending_price: string + bid_amount: string + starting_price: string + transaction: { + blockExplorerLink: string + id: string + } + assetQuantity: { + asset: { + decimals?: number + id: string + } + quantity: string + id: string + } + created_date: string +} diff --git a/packages/web3-providers/package.json b/packages/web3-providers/package.json index a95f9bbf695c..8d88fc548034 100644 --- a/packages/web3-providers/package.json +++ b/packages/web3-providers/package.json @@ -1,7 +1,11 @@ { "name": "@masknet/web3-providers", "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "dependencies": { + "@masknet/web3-shared-evm": "workspace:*", + "bignumber.js": "^9.0.1", "json-stable-stringify": "^1.0.1", "lodash-unified": "1.0.1", "urlcat": "^2.0.4" diff --git a/packages/web3-providers/rarible/constants.ts b/packages/web3-providers/rarible/constants.ts index 29827b817df1..1046095c560a 100644 --- a/packages/web3-providers/rarible/constants.ts +++ b/packages/web3-providers/rarible/constants.ts @@ -4,3 +4,4 @@ export const RaribleIPFSURL = 'https://ipfs.rarible.com/ipfs/' export const RaribleUserURL = 'https://rarible.com/user/' export const RaribleRopstenUserURL = 'https://ropsten.rarible.com/user/' export const RaribleMainnetURL = 'https://api-mainnet.rarible.com/marketplace/api/v4/' +export const RaribleMainnetAPI_URL = 'https://api-staging.rarible.com/protocol/v0.1' diff --git a/packages/web3-providers/rarible/index.ts b/packages/web3-providers/rarible/index.ts index e199d8d5bcea..8f90e8fa8b29 100644 --- a/packages/web3-providers/rarible/index.ts +++ b/packages/web3-providers/rarible/index.ts @@ -1,18 +1,45 @@ -import urlcat from 'urlcat' -import { compact } from 'lodash-unified' -import stringify from 'json-stable-stringify' -import { RaribleChainURL, RaribleMainnetURL, RaribleUserURL } from './constants' import { Ownership, + RaribleCollectibleResponse, RaribleEventType, RaribleHistory, RaribleNFTItemMapResponse, RaribleOfferResponse, RaribleProfileResponse, } from './types' -import { toRaribleImage } from './helpers' +import { + ChainId, + createLookupTableResolver, + ERC721TokenDetailed, + EthereumTokenType, + FungibleTokenDetailed, + isSameAddress, +} from '@masknet/web3-shared-evm' +import { + RaribleUserURL, + RaribleRopstenUserURL, + RaribleIPFSURL, + RaribleMainnetURL, + RaribleMainnetAPI_URL, +} from './constants' +import urlcat from 'urlcat' +import { AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' +import { compact, first } from 'lodash-unified' + +export const resolveRaribleUserNetwork = createLookupTableResolver( + { + [ChainId.Mainnet]: RaribleUserURL, + [ChainId.Ropsten]: RaribleRopstenUserURL, + }, + RaribleUserURL, +) -async function fetchFromRarible(root: string, subPath: string, config = {} as RequestInit) { +function toRaribleImage(url?: string) { + if (!url) return '' + return `${RaribleIPFSURL}${url.replace('ipfs://ipfs/', '')}` +} + +export async function fetchFromRarible(root: string, subPath: string, config = {} as RequestInit) { const response = await ( await fetch(urlcat(root, subPath), { mode: 'cors', @@ -26,38 +53,20 @@ async function fetchFromRarible(root: string, subPath: string, config = {} as export async function getProfilesFromRarible(addresses: (string | undefined)[]) { return fetchFromRarible(RaribleMainnetURL, 'profiles/list', { method: 'POST', - body: stringify(addresses), + body: JSON.stringify(addresses), headers: { 'content-type': 'application/json', }, }) } -export async function getNFTItem(tokenAddress: string, tokenId: string) { - const assetResponse = await fetchFromRarible( - RaribleChainURL, - urlcat('/v0.1/nft/items/:tokenAddress::tokenId', { - includeMeta: true, - tokenAddress, - tokenId, - }), - { - method: 'GET', - headers: { - 'content-type': 'application/json', - }, - }, - ) - return assetResponse -} - -export async function getOffersFromRarible(tokenAddress: string, tokenId: string) { +export async function getOffers(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { const orders = await fetchFromRarible( RaribleMainnetURL, `items/${tokenAddress}:${tokenId}/offers`, { method: 'POST', - body: stringify({ size: 20 }), + body: JSON.stringify({ size: 20 }), headers: { 'content-type': 'application/json', }, @@ -67,45 +76,72 @@ export async function getOffersFromRarible(tokenAddress: string, tokenId: string return orders.map((order) => { const ownerInfo = profiles.find((owner) => owner.id === order.maker) return { - unitPrice: order.buyPriceEth, - hash: order.signature, - makerAccount: { + created_time: order.updateDate, + current_price: order.buyPriceEth, + current_bounty: order.fee, + + payment_token: order.token, + listing_time: 0, + side: OrderSide.Buy, + quantity: '1', + expiration_time: 0, + order_hash: order.signature, + approved_on_chain: false, + maker_account: { user: { - username: ownerInfo?.name, + username: ownerInfo?.name ?? '', }, - address: ownerInfo?.id, + address: ownerInfo?.id ?? '', profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${RaribleUserURL}${ownerInfo?.id ?? ''}`, - }, - } + link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, + } as NFTAssetOwner, + } as AssetOrder }) } -export async function getListingsFromRarible(tokenAddress: string, tokenId: string) { +export async function getListings(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { const assets = await fetchFromRarible(RaribleMainnetURL, `items/${tokenAddress}:${tokenId}/ownerships`) const listings = assets.filter((x) => x.selling) const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) return listings.map((asset) => { const ownerInfo = profiles.find((owner) => owner.id === asset.owner) return { - unitPrice: asset.priceEth, - hash: asset.signature, - makerAccount: { + created_time: asset.date, + approved_on_chain: false, + current_price: asset.priceEth, + payment_token: asset.token, + listing_time: 0, + side: OrderSide.Buy, + quantity: '1', + expiration_time: 0, + order_hash: asset.signature, + maker_account: { user: { - username: ownerInfo?.name, + username: ownerInfo?.name ?? '', }, - address: ownerInfo?.id, + address: ownerInfo?.id ?? '', profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${RaribleUserURL}${ownerInfo?.id ?? ''}`, - }, + link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, + } as NFTAssetOwner, } }) } -export async function getHistoryFromRarible(tokenAddress: string, tokenId: string) { +export async function getOrder(tokenAddress: string, tokenId: string, side: OrderSide, chainId: ChainId) { + switch (side) { + case OrderSide.Buy: + return getOffers(tokenAddress, tokenId, chainId) + case OrderSide.Sell: + return getListings(tokenAddress, tokenId, chainId) + default: + return [] + } +} + +export async function getHistory(tokenAddress: string, tokenId: string): Promise { let histories = await fetchFromRarible(RaribleMainnetURL, `activity`, { method: 'POST', - body: stringify({ + body: JSON.stringify({ // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], filter: { '@type': 'by_item', @@ -135,9 +171,186 @@ export async function getHistoryFromRarible(tokenAddress: string, tokenId: strin const ownerInfo = profiles.find((profile) => profile.id === history.owner) const fromInfo = profiles.find((profile) => profile.id === history.buyer || profile.id === history.from) return { - ...history, - ownerInfo, - fromInfo, - } + id: history.id, + eventType: history['@type'], + timestamp: history.date.getTime() ?? 0, + price: { + quantity: '1', + price: history.price, + asset: { + id: fromInfo?.id, + decimals: 0, + image_url: fromInfo?.image, + image_original_url: '', + image_preview_url: '', + asset_contract: { + symbol: fromInfo?.type, + }, + permalink: '', + }, + }, + accountPair: { + from: { + username: fromInfo?.name, + address: fromInfo?.id, + imageUrl: fromInfo?.image, + link: '', + }, + to: { + username: ownerInfo?.name, + address: ownerInfo?.id, + imageUrl: ownerInfo?.image, + link: '', + }, + }, + } as NFTHistory }) } + +//#region new code + +function createERC721TokenAsset( + tokenAddress: string, + tokenId: string, + asset?: RaribleNFTItemMapResponse, +): ERC721TokenDetailed { + return { + contractDetailed: { + type: EthereumTokenType.ERC721, + chainId: ChainId.Mainnet, + address: tokenAddress, + name: asset?.meta.name ?? '', + symbol: '', + }, + info: { + name: asset?.meta.name ?? '', + description: asset?.meta.description ?? '', + image: asset?.meta.image.url.ORIGINAL ?? asset?.meta.image.url.PREVIEW ?? '', + owner: asset?.owners[0], + }, + tokenId: tokenId, + } +} + +function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { + const owner = first(asset?.owners) + const creator = first(asset?.creators) + return { + is_verified: false, + is_auction: false, + image_url: asset?.meta.image.url.PREVIEW, + asset_contract: null, + owner: owner + ? { + address: owner, + profile_img_url: '', + user: { username: owner }, + link: '', + } + : null, + creator: creator + ? { + address: creator.account, + profile_img_url: '', + user: { username: creator.account }, + link: '', + } + : null, + traits: asset?.meta.attributes.map(({ key, value }) => ({ trait_type: key, value })), + description: asset?.meta.description ?? '', + name: asset?.meta.name ?? 'Unknown', + collection_name: '', + animation_url: asset.meta.animation?.url.PREVIEW, + current_price: 0, + current_symbol: 'ETH', + end_time: null, + order_payment_tokens: [] as FungibleTokenDetailed[], + offer_payment_tokens: [] as FungibleTokenDetailed[], + + slug: '', + response_: asset, + } as NFTAsset +} + +async function _getAsset(address: string, tokenId: string) { + return fetchFromRarible( + RaribleMainnetAPI_URL, + `/ethereum/nft/items/${address}:${tokenId}`, + {}, + ) +} + +export async function getAsset(address: string, tokenId: string, chainId: ChainId) { + const asset = await _getAsset(address, tokenId) + if (!asset) return + + return createNFTAsset(asset, chainId) +} + +export async function getNFT(tokenAddress: string, tokenId: string) { + const asset = await _getAsset(tokenAddress, tokenId) + return createERC721TokenAsset(tokenAddress, tokenId, asset) +} + +export async function getNFTs(from: string, chainId: ChainId) { + const params = new URLSearchParams() + + params.append('owner', from) + const asset = await fetchFromRarible<{ + total: number + continuation: string + items: RaribleNFTItemMapResponse[] + }>(RaribleMainnetAPI_URL, `/ethereum/nft/items/byOwner?${params.toString()}`, {}) + if (!asset) return [] as ERC721TokenDetailed[] + + return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) +} + +export async function getContractBalance(from: string, contract_address: string, chainId: ChainId) { + /* + const balanceResponse = await fetchFromRarible<{ + contract: string + owner: string + balance: string + decimalBalance: string + }>(RaribleMainnetURL, `/ethereum/erc20/balances/${contract_address}/${from}`, {}) + + return formatBalance( + new BigNumber(balanceResponse.balance), + new BigNumber(balanceResponse.decimalBalance).toNumber(), + ) + */ + const assets = await getNFTs(from, chainId) + return assets.filter((asset) => isSameAddress(asset.contractDetailed.address, contract_address)).length +} + +export async function getContract(from: string, contract_address: string, chainId: ChainId) { + return +} + +export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { + const params = new URLSearchParams() + + params.append('owner', from) + params.append('size', (opts.size ?? '0').toString()) + const asset = await fetchFromRarible<{ + total: number + continuation: string + items: RaribleNFTItemMapResponse[] + }>(RaribleMainnetAPI_URL, `/ethereum/nft/items/byOwner?${params.toString()}`, {}) + if (!asset) return [] as ERC721TokenDetailed[] + + return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) +} + +export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { + const response = await fetchFromRarible(RaribleMainnetURL, `collections/${address}}`, { + method: 'POST', + body: JSON.stringify({ size: 20 }), + headers: { + 'content-type': 'application/json', + }, + }) + + return [response] +} diff --git a/packages/web3-providers/rarible/types.ts b/packages/web3-providers/rarible/types.ts index 3efd9b42663a..672a023c8121 100644 --- a/packages/web3-providers/rarible/types.ts +++ b/packages/web3-providers/rarible/types.ts @@ -32,9 +32,9 @@ export interface Ownership { tokenId: string owner: string value: number - date: Date - price: number - priceEth: number + date: string + price: string + priceEth: string buyToken: string buyTokenId: string status: string @@ -167,15 +167,15 @@ export interface RaribleOfferResponse { buyAssetType: string value: number signature: string - updateDate: Date + updateDate: string importantUpdateDate: Date updateStateDate: Date contractVersion: number - fee: number + fee: string sold: number canceled: boolean pending: RaribleTransferItem[] - buyPriceEth: number + buyPriceEth: string version: number id: string active: boolean @@ -199,8 +199,8 @@ export interface RaribleHistory { '@type': RaribleEventType id: string owner: string - value: number - price: number + value: string + price: string buyToken: string buyTokenId: string buyer?: string diff --git a/packages/web3-providers/tsconfig.json b/packages/web3-providers/tsconfig.json index a60b8d557dca..5e2ba1dc3143 100644 --- a/packages/web3-providers/tsconfig.json +++ b/packages/web3-providers/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./src/", + "rootDir": "./", "outDir": "./dist/", "tsBuildInfoFile": "./dist/.tsbuildinfo" }, - "include": ["./src", "./src/**/*.json"] + "include": ["./", "./**/*.json"], + "references": [{ "path": "../web3-shared/evm" }] } diff --git a/packages/web3-providers/types.ts b/packages/web3-providers/types.ts new file mode 100644 index 000000000000..7dcf46aa7625 --- /dev/null +++ b/packages/web3-providers/types.ts @@ -0,0 +1,137 @@ +import type { ERC20TokenDetailed, NativeTokenDetailed } from '@masknet/web3-shared-evm' + +export enum OrderSide { + Buy = 0, + Sell = 1, +} +export interface NFTAssetOwner { + address: string + profile_img_url?: string + user?: { + username: string + } + link: string +} + +export interface NFTAssetTrait { + trait_type: string + value: string +} + +export interface AssetToken { + image_url?: string + eth_price?: string + usd_price?: string + name: string + symbol: string + decimals: number + address: string +} + +export interface AssetOrder { + created_time?: string + current_price?: string + current_bounty?: string + maker_account?: NFTAssetOwner + taker_account?: NFTAssetOwner + payment_token?: string + payment_token_contract?: AssetToken + fee_recipient_account?: AssetToken + cancelled_or_finalized?: boolean + marked_invalid?: boolean + approved_on_chain: boolean + listing_time: number + side: number + quantity: string + expiration_time: number + order_hash: string +} + +export interface AssetCollection { + name: string + slug: string + editors: string[] + hidden: boolean + featured: boolean + created_date: string + description: string + image_url: string + largeImage_url: string + featured_image_url: string + stats: object + display_data: object + payment_tokens: AssetToken[] + payout_address?: string + trait_stats: NFTAssetTrait + external_link?: string + wiki_link?: string + safelist_request_status: string +} + +export interface NFTAsset { + is_verified: boolean + collection: AssetCollection | null + is_auction: boolean + image_url: string + asset_contract: { name: string; description: string; schemaName: string } | null + current_price: number | null + current_symbol: string + owner: NFTAssetOwner | null + creator: NFTAssetOwner | null + token_id: string + token_address: string + traits: NFTAssetTrait[] + safelist_request_status: string + description: string + name: string + collection_name: string + animation_url: string + end_time: Date | null + order_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] + offer_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] + slug: string | null + desktopOrder: AssetOrder | null + top_ownerships: { + owner: NFTAssetOwner + }[] + + response_: any +} + +export interface NFTHistory { + id: string + accountPair: { + from?: { + username?: string + address?: string + imageUrl?: string + link: string + } + to?: { + username?: string + address?: string + imageUrl?: string + link: string + } + } + price?: { + quantity: string + price: string + asset?: { + id: string + decimals: number + image_url: string + image_original_url: string + image_preview_url: string + asset_contract: { + symbol: string + } + permalink: string + } + paymentToken?: AssetToken + } + + eventType: string + transactionBlockExplorerLink?: string + timestamp: number +} diff --git a/packages/web3-providers/utils/index.ts b/packages/web3-providers/utils/index.ts new file mode 100644 index 000000000000..9bba10f0ce1b --- /dev/null +++ b/packages/web3-providers/utils/index.ts @@ -0,0 +1,36 @@ +import { ChainId, createERC20Token, createNativeToken, formatBalance } from '@masknet/web3-shared-evm' +import BigNumber from 'bignumber.js' + +export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { + if (!currentPrice || !decimals || !quantity) return + const price = formatBalance(currentPrice, decimals) + const _quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) + return new BigNumber(price).dividedBy(_quantity).toFixed(4, 1).toString() +} + +export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { + if (!currentPrice || !decimals) return + const price = formatBalance(usdPrice, 0) + const quantity = formatBalance(currentPrice, decimals) + + return new BigNumber(price).multipliedBy(quantity).toFixed(2, 1).toString() +} + +export function getLastSalePrice(total_price?: string, decimals?: number) { + if (!total_price || !decimals) return + const price = formatBalance(total_price, decimals) + return price +} + +export function toDate(timestamp: number) { + if (timestamp === 0) return null + return new Date(timestamp * 1000) +} + +export function toTokenDetailed( + chainId: ChainId, + token: { address: string; decimals: number; name: string; symbol: string }, +) { + if (token.symbol === 'ETH') return createNativeToken(chainId) + return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) +} diff --git a/packages/web3-shared/evm/pipes/index.ts b/packages/web3-shared/evm/pipes/index.ts index 43f429dc2762..6f0219f36681 100644 --- a/packages/web3-shared/evm/pipes/index.ts +++ b/packages/web3-shared/evm/pipes/index.ts @@ -135,6 +135,10 @@ export function resolveCollectibleProviderLink(chainId: ChainId, provider: NonFu case NonFungibleAssetProvider.OPENSEA: if (chainId === ChainId.Rinkeby) return `https://testnets.opensea.io` return `https://opensea.io` + case NonFungibleAssetProvider.RARIBLE: + return 'https://rarible.com' + case NonFungibleAssetProvider.NFTSCAN: + return 'https:/nftscan.com' default: unreachable(provider) } @@ -146,6 +150,10 @@ export function resolveCollectibleAssetLink(chainId: ChainId, provider: NonFungi if (chainId === ChainId.Rinkeby) return `https://testnets.opensea.io/assets` if (chainId === ChainId.Matic) return `https://opensea.io/assets/matic` return `https://opensea.io/assets` + case NonFungibleAssetProvider.RARIBLE: + return '' + case NonFungibleAssetProvider.NFTSCAN: + return '' default: unreachable(provider) } @@ -162,6 +170,10 @@ export function resolveCollectibleLink( address, tokenId, }) + case NonFungibleAssetProvider.RARIBLE: + return '' + case NonFungibleAssetProvider.NFTSCAN: + return '' default: unreachable(provider) } diff --git a/packages/web3-shared/evm/types/index.ts b/packages/web3-shared/evm/types/index.ts index 2b909cfdd29f..01e8fe0d51f5 100644 --- a/packages/web3-shared/evm/types/index.ts +++ b/packages/web3-shared/evm/types/index.ts @@ -520,7 +520,9 @@ export enum FungibleAssetProvider { } export enum NonFungibleAssetProvider { - OPENSEA = 'OpenSea', + OPENSEA = 0, + RARIBLE = 1, + NFTSCAN = 2, } export type UnboxTransactionObject = T extends NonPayableTransactionObject diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4c3e87f9f88..45909c0ea6af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,6 +301,7 @@ importers: '@masknet/shared-base': workspace:* '@masknet/theme': workspace:* '@masknet/web3-contracts': workspace:* + '@masknet/web3-providers': workspace:* '@masknet/web3-shared-evm': workspace:* '@msgpack/msgpack': ^2.7.1 '@nice-labs/emit-file-webpack-plugin': ^1.1.2 @@ -437,6 +438,7 @@ importers: '@masknet/shared-base': link:../shared-base '@masknet/theme': link:../theme '@masknet/web3-contracts': link:../web3-contracts + '@masknet/web3-providers': link:../web3-providers '@masknet/web3-shared-evm': link:../web3-shared/evm '@msgpack/msgpack': 2.7.1 '@servie/events': 3.0.0 @@ -849,10 +851,14 @@ importers: packages/web3-providers: specifiers: + '@masknet/web3-shared-evm': workspace:* + bignumber.js: ^9.0.1 json-stable-stringify: ^1.0.1 lodash-unified: 1.0.1 urlcat: ^2.0.4 dependencies: + '@masknet/web3-shared-evm': link:../web3-shared/evm + bignumber.js: 9.0.1 json-stable-stringify: 1.0.1 lodash-unified: 1.0.1_bd4845f8c1bf1d81a616b60758034d0a urlcat: 2.0.4 From b931609449806215e775f588aaaa5295c47898f3 Mon Sep 17 00:00:00 2001 From: lelenei <72531217+lelenei@users.noreply.github.com> Date: Sun, 5 Dec 2021 21:01:03 +0800 Subject: [PATCH 02/24] fix: read rarible --- packages/web3-providers/rarible/constants.ts | 1 + packages/web3-providers/rarible/index.ts | 66 +++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/packages/web3-providers/rarible/constants.ts b/packages/web3-providers/rarible/constants.ts index 1046095c560a..d6baed4a413d 100644 --- a/packages/web3-providers/rarible/constants.ts +++ b/packages/web3-providers/rarible/constants.ts @@ -5,3 +5,4 @@ export const RaribleUserURL = 'https://rarible.com/user/' export const RaribleRopstenUserURL = 'https://ropsten.rarible.com/user/' export const RaribleMainnetURL = 'https://api-mainnet.rarible.com/marketplace/api/v4/' export const RaribleMainnetAPI_URL = 'https://api-staging.rarible.com/protocol/v0.1' +export const RaribleMainnetSearchURL = 'https://api-mainnet.rarible.com/marketplace/search/v1/' diff --git a/packages/web3-providers/rarible/index.ts b/packages/web3-providers/rarible/index.ts index 8f90e8fa8b29..d1b13910cd5a 100644 --- a/packages/web3-providers/rarible/index.ts +++ b/packages/web3-providers/rarible/index.ts @@ -18,13 +18,14 @@ import { import { RaribleUserURL, RaribleRopstenUserURL, - RaribleIPFSURL, RaribleMainnetURL, RaribleMainnetAPI_URL, + RaribleChainURL, } from './constants' import urlcat from 'urlcat' import { AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' import { compact, first } from 'lodash-unified' +import { toRaribleImage } from './helpers' export const resolveRaribleUserNetwork = createLookupTableResolver( { @@ -34,11 +35,6 @@ export const resolveRaribleUserNetwork = createLookupTableResolver(root: string, subPath: string, config = {} as RequestInit) { const response = await ( await fetch(urlcat(root, subPath), { @@ -225,7 +221,7 @@ function createERC721TokenAsset( info: { name: asset?.meta.name ?? '', description: asset?.meta.description ?? '', - image: asset?.meta.image.url.ORIGINAL ?? asset?.meta.image.url.PREVIEW ?? '', + image: toRaribleImage(asset?.meta.image.url.ORIGINAL ?? asset?.meta.image.url.PREVIEW ?? ''), owner: asset?.owners[0], }, tokenId: tokenId, @@ -238,7 +234,8 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { return { is_verified: false, is_auction: false, - image_url: asset?.meta.image.url.PREVIEW, + token_address: asset.contract, + image_url: toRaribleImage(asset?.meta.image.url.ORIGINAL), asset_contract: null, owner: owner ? { @@ -266,18 +263,40 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { end_time: null, order_payment_tokens: [] as FungibleTokenDetailed[], offer_payment_tokens: [] as FungibleTokenDetailed[], - + top_ownerships: owner + ? [ + { + owner: { + address: owner, + profile_img_url: '', + user: { username: owner }, + link: '', + }, + }, + ] + : null, slug: '', response_: asset, } as NFTAsset } async function _getAsset(address: string, tokenId: string) { - return fetchFromRarible( - RaribleMainnetAPI_URL, - `/ethereum/nft/items/${address}:${tokenId}`, - {}, + const assetResponse = await fetchFromRarible( + RaribleChainURL, + urlcat('/v0.1/nft/items/:address::tokenId', { + includeMeta: true, + address, + tokenId, + }), + { + method: 'GET', + mode: 'cors', + headers: { + 'content-type': 'application/json', + }, + }, ) + return assetResponse } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { @@ -294,16 +313,19 @@ export async function getNFT(tokenAddress: string, tokenId: string) { export async function getNFTs(from: string, chainId: ChainId) { const params = new URLSearchParams() - params.append('owner', from) - const asset = await fetchFromRarible<{ - total: number - continuation: string - items: RaribleNFTItemMapResponse[] - }>(RaribleMainnetAPI_URL, `/ethereum/nft/items/byOwner?${params.toString()}`, {}) - if (!asset) return [] as ERC721TokenDetailed[] - - return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) + const assetResponse = await fetchFromRarible<{ total: number; items: RaribleNFTItemMapResponse[] }>( + RaribleChainURL, + `/v0.1/nft/items/byOwner?${params.toString()}`, + { + method: 'GET', + mode: 'cors', + headers: { + 'content-type': 'application/json', + }, + }, + ) + return assetResponse.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) } export async function getContractBalance(from: string, contract_address: string, chainId: ChainId) { From 28dbffb8ee1106f543972b1d509461f191099c88 Mon Sep 17 00:00:00 2001 From: lelenei <72531217+lelenei@users.noreply.github.com> Date: Sat, 11 Dec 2021 13:19:36 +0800 Subject: [PATCH 03/24] fix: typo --- .../plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx | 2 +- .../src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx index 5b115cf6e70f..5f39a1efcaa3 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx @@ -75,7 +75,7 @@ export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract.schema_name, + schemaName: asset.value.asset_contract?.schemaName, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx index fff283a398d8..d932e84f804c 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx @@ -105,7 +105,7 @@ export function ListingByPriceCard(props: ListingByPriceCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract.schema_name, + schemaName: asset.value.asset_contract?.schemaName, }), accountAddress: account, startAmount: Number.parseFloat(amount), From ef5723e8b84880e6555fc2bfb95473a046f013ce Mon Sep 17 00:00:00 2001 From: lelenei <72531217+lelenei@users.noreply.github.com> Date: Mon, 13 Dec 2021 09:40:34 +0800 Subject: [PATCH 04/24] fix: reply review --- .../mask/src/plugins/EVM/hooks/useAsset.ts | 2 +- packages/web3-providers/package.json | 3 +- packages/web3-providers/src/opensea/index.ts | 35 +++++++++++-------- packages/web3-providers/src/rarible/index.ts | 19 ---------- packages/web3-providers/src/utils/index.ts | 5 --- packages/web3-providers/tsconfig.json | 2 +- pnpm-lock.yaml | 9 ++++- 7 files changed, 33 insertions(+), 42 deletions(-) diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts index 021ba4e5bfe8..55c271a24b8c 100644 --- a/packages/mask/src/plugins/EVM/hooks/useAsset.ts +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -26,5 +26,5 @@ export function useAsset(address: string, token_id: string, provider: NonFungibl isSameAddress(item.owner.address, account), ) ?? false, } - }, [account, chainId, WNATIVE_ADDRESS]) + }, [account, chainId, WNATIVE_ADDRESS, address, token_id, provider]) } diff --git a/packages/web3-providers/package.json b/packages/web3-providers/package.json index 17a28af3e1ed..974961f51087 100644 --- a/packages/web3-providers/package.json +++ b/packages/web3-providers/package.json @@ -4,9 +4,10 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { + "@masknet/plugin-infra": "workspace:*", "@masknet/web3-shared-evm": "workspace:*", "bignumber.js": "^9.0.1", - "@masknet/plugin-infra": "workspace:*", + "date-fns": "^2.27.0", "json-stable-stringify": "^1.0.1", "lodash-unified": "1.0.1", "urlcat": "^2.0.4" diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index a20094fbaed0..f5e88e7c3546 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -3,7 +3,8 @@ import type { OpenSeaAssetContract, OpenSeaAssetEvent, OpenSeaResponse, OpenSeaC import urlcat from 'urlcat' import { head, uniqBy } from 'lodash-unified' import type { AssetCollection, AssetOrder, NFTAsset, NFTHistory, OrderSide } from '../types' -import { getOrderUnitPrice, getOrderUSDPrice, toDate, toTokenDetailed } from '../utils' +import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from '../utils' +import fromUnixTime from 'date-fns/fromUnixTime' import BigNumber from 'bignumber.js' import { @@ -40,7 +41,13 @@ export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page const asset = await fetchAsset(`${OpenSea_API_URL}/api/v1/assets?${params.toString()}`, chainId) if (!asset) return [] - return asset.assets.map((asset: OpenSeaResponse) => createERC721TokenAsset(from, asset.token_id, chainId, asset)) + return asset.assets + .filter( + (x: OpenSeaResponse) => + ['non-fungible', 'semi-fungible'].includes(x.asset_contract.type) || + ['ERC721', 'ERC1155'].includes(x.asset_contract.schema_name), + ) + .map((asset: OpenSeaResponse) => createERC721TokenAsset(from, asset.token_id, chainId, asset)) } function createERC721ContractDetailedFromAssetContract( @@ -151,7 +158,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { end_time: asset.endTime ? new Date(asset.endTime) : desktopOrder - ? toDate(Number.parseInt(desktopOrder.listing_time as unknown as string, 10)) + ? fromUnixTime(Number.parseInt(desktopOrder.listing_time as unknown as string, 10)) : null, order_payment_tokens: desktopOrder?.payment_token_contract ? [toTokenDetailed(chainId, desktopOrder.payment_token_contract)] @@ -262,18 +269,17 @@ async function fetchOrder( params.append('side', side.toString()) params.append('offset', page.toString()) params.append('limit', size.toString()) - const response = await ( - await fetch(`${OpenSea_API_URL}/wyvern/v1/orders?${params.toString()}`, { - method: 'GET', - mode: 'cors', - headers: { - Accept: 'application/json', - 'x-api-key': OPENSEA_API_KEY, - }, - }) - ).json() + const response = await fetch(`${OpenSea_API_URL}/wyvern/v1/orders?${params.toString()}`, { + method: 'GET', + mode: 'cors', + headers: { + Accept: 'application/json', + 'x-api-key': OPENSEA_API_KEY, + }, + }) - const { orders }: { orders: AssetOrder[] } = response + if (!response.ok) return [] + const { orders = [] }: { orders: AssetOrder[] } = await response.json() return orders } @@ -285,6 +291,7 @@ export async function getOrder(address: string, tokenId: string, side: OrderSide let order do { order = await fetchOrder(address, tokenId, side, page, size, chainId) + if (order.length === 0) break orders = orders.concat(order ?? []) page = page + 1 } while (order.length === size) diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index d1b13910cd5a..0c3915c60c5b 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -203,8 +203,6 @@ export async function getHistory(tokenAddress: string, tokenId: string): Promise }) } -//#region new code - function createERC721TokenAsset( tokenAddress: string, tokenId: string, @@ -329,27 +327,10 @@ export async function getNFTs(from: string, chainId: ChainId) { } export async function getContractBalance(from: string, contract_address: string, chainId: ChainId) { - /* - const balanceResponse = await fetchFromRarible<{ - contract: string - owner: string - balance: string - decimalBalance: string - }>(RaribleMainnetURL, `/ethereum/erc20/balances/${contract_address}/${from}`, {}) - - return formatBalance( - new BigNumber(balanceResponse.balance), - new BigNumber(balanceResponse.decimalBalance).toNumber(), - ) - */ const assets = await getNFTs(from, chainId) return assets.filter((asset) => isSameAddress(asset.contractDetailed.address, contract_address)).length } -export async function getContract(from: string, contract_address: string, chainId: ChainId) { - return -} - export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const params = new URLSearchParams() diff --git a/packages/web3-providers/src/utils/index.ts b/packages/web3-providers/src/utils/index.ts index 9bba10f0ce1b..eb22e792c41f 100644 --- a/packages/web3-providers/src/utils/index.ts +++ b/packages/web3-providers/src/utils/index.ts @@ -22,11 +22,6 @@ export function getLastSalePrice(total_price?: string, decimals?: number) { return price } -export function toDate(timestamp: number) { - if (timestamp === 0) return null - return new Date(timestamp * 1000) -} - export function toTokenDetailed( chainId: ChainId, token: { address: string; decimals: number; name: string; symbol: string }, diff --git a/packages/web3-providers/tsconfig.json b/packages/web3-providers/tsconfig.json index 942c7ba72c9c..e2eb901529ec 100644 --- a/packages/web3-providers/tsconfig.json +++ b/packages/web3-providers/tsconfig.json @@ -6,5 +6,5 @@ "tsBuildInfoFile": "./dist/.tsbuildinfo" }, "include": ["./src", "./src/**/*.json"], - "references": [{ "path": "../plugin-infra" }, { "path": "../web3-shared/evm" }] + "references": [{ "path": "../plugin-infra" }] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e6a2c71d49c..ac4a55fbae93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -868,6 +868,7 @@ importers: '@masknet/plugin-infra': workspace:* '@masknet/web3-shared-evm': workspace:* bignumber.js: ^9.0.1 + date-fns: ^2.27.0 json-stable-stringify: ^1.0.1 lodash-unified: 1.0.1 urlcat: ^2.0.4 @@ -875,6 +876,7 @@ importers: '@masknet/plugin-infra': link:../plugin-infra '@masknet/web3-shared-evm': link:../web3-shared/evm bignumber.js: 9.0.1 + date-fns: 2.27.0 json-stable-stringify: 1.0.1 lodash-unified: 1.0.1_bd4845f8c1bf1d81a616b60758034d0a urlcat: 2.0.4 @@ -14007,6 +14009,11 @@ packages: engines: {node: '>=0.11'} dev: false + /date-fns/2.27.0: + resolution: {integrity: sha512-sj+J0Mo2p2X1e306MHq282WS4/A8Pz/95GIFcsPNMPMZVI3EUrAdSv90al1k+p74WGLCruMXk23bfEDZa71X9Q==} + engines: {node: '>=0.11'} + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} dependencies: @@ -24456,7 +24463,7 @@ packages: dependencies: commander: 2.20.3 source-map: 0.7.3 - source-map-support: 0.5.20 + source-map-support: 0.5.21 dev: true /test-exclude/6.0.0: From 438b371d7e97afcf789ed121228e5b79810d25ae Mon Sep 17 00:00:00 2001 From: lelenei Date: Wed, 15 Dec 2021 19:39:56 +0800 Subject: [PATCH 05/24] fix: reply review --- .../mask/src/plugins/Avatar/utils/index.ts | 3 +- .../Collectible/SNSAdaptor/ActionBar.tsx | 8 +- .../Collectible/SNSAdaptor/CheckoutDialog.tsx | 4 +- .../SNSAdaptor/MakeOfferDialog.tsx | 4 +- .../Collectible/SNSAdaptor/OrderRow.tsx | 13 +- .../mask/src/plugins/Collectible/helpers.ts | 7 +- .../Collectible/hooks/useCollectibleState.ts | 1 - .../mask/src/plugins/EVM/hooks/useAsset.ts | 12 +- .../src/plugins/EVM/hooks/useNFTBalance.ts | 8 +- .../src/plugins/EVM/services/NFT/getNFT.ts | 2 +- .../plugins/EVM/services/NFT/getNFTBalance.ts | 8 +- .../src/plugins/EVM/services/NFT/getNFTs.ts | 2 +- packages/web3-providers/package.json | 1 + packages/web3-providers/src/NFTScan/index.ts | 12 +- packages/web3-providers/src/opensea/index.ts | 124 +++++++++++------- packages/web3-providers/src/rarible/index.ts | 20 +-- packages/web3-providers/src/types.ts | 2 +- packages/web3-shared/base/utils/number.ts | 5 + pnpm-lock.yaml | 2 + 19 files changed, 129 insertions(+), 109 deletions(-) diff --git a/packages/mask/src/plugins/Avatar/utils/index.ts b/packages/mask/src/plugins/Avatar/utils/index.ts index 3f350f8734b9..c0943c106a25 100644 --- a/packages/mask/src/plugins/Avatar/utils/index.ts +++ b/packages/mask/src/plugins/Avatar/utils/index.ts @@ -24,8 +24,7 @@ export async function getNFT(address: string, tokenId: string) { } export async function createNFT(address: string, tokenId: string) { - const nft = await EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet) - return nft + return EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet) } export async function getImage(image: string): Promise { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx index 08a6c02d00f4..d0e120af866d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx @@ -46,7 +46,7 @@ export function ActionBar(props: ActionBarProps) { return ( - {!asset.value.is_owner && asset.value.is_auction ? ( + {!asset.value.isOwner && asset.value.isAuction ? ( ) : null} - {!asset.value.is_owner && !asset.value.is_auction && asset.value?.desktopOrder ? ( + {!asset.value.isOwner && !asset.value.isAuction && asset.value?.desktopOrder ? ( ) : null} - {!asset.value.is_owner && !asset.value.is_auction ? ( + {!asset.value.isOwner && !asset.value.isAuction ? ( ) : null} - {asset.value.is_owner ? ( + {asset.value.isOwner ? ( - {asset?.value?.is_order_weth ? ( + {asset?.value?.isOrderWeth ? ( - {(isAuction ? asset.value?.is_collection_weth : asset.value?.is_order_weth) ? ( + {(isAuction ? asset.value?.isCollectionWeth : asset.value?.isOrderWeth) ? ( { return { @@ -156,14 +156,7 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { {order.expiration_time && !isZero(order.expiration_time) && - formatDistanceToNow( - new Date( - new BigNumber(order.expiration_time ?? 0).multipliedBy(1000).toNumber(), - ), - { - addSuffix: true, - }, - )} + fromUnixTime(multipliedBy(order.expiration_time, 1000).toNumber())} ) : null} diff --git a/packages/mask/src/plugins/Collectible/helpers.ts b/packages/mask/src/plugins/Collectible/helpers.ts index 5f1321a6480b..820beff10440 100644 --- a/packages/mask/src/plugins/Collectible/helpers.ts +++ b/packages/mask/src/plugins/Collectible/helpers.ts @@ -1,7 +1,7 @@ import type { Asset, OpenSeaFungibleToken, WyvernSchemaName } from 'opensea-js/lib/types' import { ChainId, createERC20Token, createNativeToken } from '@masknet/web3-shared-evm' import { createRenderWithMetadata, createTypedMessageMetadataReader } from '../../protocols/typed-message' -import { PLUGIN_META_KEY, RaribleIPFSURL } from './constants' +import { PLUGIN_META_KEY } from './constants' import type { CollectibleJSON_Payload, CollectibleToken } from './types' import schema from './schema.json' import type BigNumber from 'bignumber.js' @@ -34,8 +34,3 @@ export function toTokenIdentifier(token?: CollectibleToken) { export function toDecimalAmount(weiAmount: BigNumber.Value, decimals: number) { return leftShift(weiAmount, decimals).toNumber() } - -export function toRaribleImage(url?: string) { - if (!url) return '' - return `${RaribleIPFSURL}${url.replace('ipfs://ipfs/', '')}` -} diff --git a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts index 726b5f7828d0..f76f29dc87bc 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts @@ -12,7 +12,6 @@ function useCollectibleState(token?: CollectibleToken) { currentCollectibleProviderSettings.value = token?.provider ?? NonFungibleAssetProvider.OPENSEA - //const provider = useValueRef(currentCollectibleProviderSettings) const asset = useAsset( token?.contractAddress ?? '', token?.tokenId ?? '', diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts index 55c271a24b8c..c231f40d391f 100644 --- a/packages/mask/src/plugins/EVM/hooks/useAsset.ts +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -9,22 +9,22 @@ import { } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' -export function useAsset(address: string, token_id: string, provider: NonFungibleAssetProvider) { +export function useAsset(address: string, tokenId: string, provider: NonFungibleAssetProvider) { const account = useAccount() const chainId = useChainId() const { WNATIVE_ADDRESS } = useTokenConstants() return useAsyncRetry(async () => { - const asset = await EVM_RPC.getAsset(address, token_id, chainId, provider) + const asset = await EVM_RPC.getAsset(address, tokenId, chainId, provider) return { ...asset, - is_order_weth: isSameAddress(asset?.desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS) ?? false, - is_collection_weth: asset?.collection?.payment_tokens?.some(currySameAddress(WNATIVE_ADDRESS)) ?? false, - is_owner: + isOrderWeth: isSameAddress(asset?.desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS) ?? false, + isCollectionWeth: asset?.collection?.payment_tokens?.some(currySameAddress(WNATIVE_ADDRESS)) ?? false, + isOwner: asset?.top_ownerships.some((item: { owner: { address: string | undefined } }) => isSameAddress(item.owner.address, account), ) ?? false, } - }, [account, chainId, WNATIVE_ADDRESS, address, token_id, provider]) + }, [account, chainId, WNATIVE_ADDRESS, address, tokenId, provider]) } diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts index cf175b2cf8ad..cf63616044a4 100644 --- a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts +++ b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts @@ -2,10 +2,10 @@ import { useAsyncRetry } from 'react-use' import { useChainId } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' -export function useNFTBalance(address: string, contract_address?: string) { +export function useNFTBalance(address: string, contractAddress?: string) { const chainId = useChainId() return useAsyncRetry(async () => { - if (!contract_address) return - return EVM_RPC.getNFTBalance(address, contract_address, chainId) - }, [address, contract_address, chainId]) + if (!contractAddress) return + return EVM_RPC.getNFTBalance(address, contractAddress, chainId) + }, [address, contractAddress, chainId]) } diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts index 6200d30f3a21..ee72597ce8a5 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts @@ -15,7 +15,7 @@ export async function getNFT( token = await OpenSeaApi.getNFT(address, tokenId, chainId) return token case NonFungibleAssetProvider.NFTSCAN: - token = await NFTScanApi.getNFT(address, tokenId) + token = await NFTScanApi.getNFT(address, tokenId, chainId) return token case NonFungibleAssetProvider.RARIBLE: token = await RaribleApi.getNFT(address, tokenId) diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts index 599cd307c271..537ba90169bb 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts @@ -5,20 +5,20 @@ import { currentChainIdSettings } from '../../../Wallet/settings' export async function getNFTBalance( address: string, - contract_address: string, + contractAddress: string, chainId = currentChainIdSettings.value, provider = NonFungibleAssetProvider.OPENSEA, ) { let balance switch (provider) { case NonFungibleAssetProvider.OPENSEA: - balance = await OpenSeaApi.getContractBalance(address, contract_address, chainId) + balance = await OpenSeaApi.getContractBalance(address, contractAddress, chainId) return balance case NonFungibleAssetProvider.NFTSCAN: - balance = await NFTScanApi.getContractBalance(address, contract_address, chainId) + balance = await NFTScanApi.getContractBalance(address, contractAddress, chainId) return balance case NonFungibleAssetProvider.RARIBLE: - balance = await RaribleApi.getContractBalance(address, contract_address, chainId) + balance = await RaribleApi.getContractBalance(address, contractAddress, chainId) return balance default: unreachable(provider) diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts index d483fd4de356..10ced85d1225 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts @@ -14,7 +14,7 @@ export async function getNFTs( tokens = await OpenSeaApi.getNFTs(from, chainId) return tokens case NonFungibleAssetProvider.NFTSCAN: - tokens = await NFTScanApi.getNFTs(from) + tokens = await NFTScanApi.getNFTs(from, chainId) return tokens case NonFungibleAssetProvider.RARIBLE: tokens = await RaribleApi.getNFTs(from, chainId) diff --git a/packages/web3-providers/package.json b/packages/web3-providers/package.json index 974961f51087..fab7ba579e2e 100644 --- a/packages/web3-providers/package.json +++ b/packages/web3-providers/package.json @@ -6,6 +6,7 @@ "dependencies": { "@masknet/plugin-infra": "workspace:*", "@masknet/web3-shared-evm": "workspace:*", + "@masknet/web3-shared-base": "workspace:*", "bignumber.js": "^9.0.1", "date-fns": "^2.27.0", "json-stable-stringify": "^1.0.1", diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index f780f8235dd8..6bfdb97cab85 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -60,7 +60,7 @@ function createERC721TokenAsset(asset: NFTScanAsset) { { name: json.name ?? 'unknown name', description: json.description ?? 'unknown symbol', - image: json.image || '', + mediaUrl: json.image || '', owner: asset.nft_holder ?? '', }, asset.token_id, @@ -97,7 +97,7 @@ async function getContractsAndBalance(address: string) { .sort((a, b) => a.balance - b.balance) } -export async function getNFT(address: string, tokenId: string, chainId = ChainId.Mainnet) { +export async function getNFT(address: string, tokenId: string, chainId: ChainId) { const response = await fetchAsset('getSingleNft', { body: JSON.stringify({ nft_address: address, @@ -111,14 +111,14 @@ export async function getNFT(address: string, tokenId: string, chainId = ChainId return createERC721TokenAsset(data) } -export async function getNFTs(from: string, chainId = ChainId.Mainnet) { +export async function getNFTs(from: string, chainId: ChainId) { let tokens: ERC721TokenDetailed[] = [] let page = 0 let assets const size = 50 do { assets = await getNFTsPaged(from, { chainId, page, size }) - if (!assets) return [] + if (assets.length === 0) break tokens = tokens.concat(assets) page = page + 1 } while (assets.length === size) @@ -145,11 +145,11 @@ export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page? return data.content.map((asset) => createERC721TokenAsset(asset)) } -export async function getContractBalance(address: string, contract_address: string, chainId: ChainId) { +export async function getContractBalance(address: string, contractAddress: string, chainId: ChainId) { const response = await getContractsAndBalance(address) if (!response) return - return response.find((x) => isSameAddress(x.contractDetailed.address, contract_address))?.balance + return response.find((x) => isSameAddress(x.contractDetailed.address, contractAddress))?.balance } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index f5e88e7c3546..39e79dd19688 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -9,8 +9,8 @@ import fromUnixTime from 'date-fns/fromUnixTime' import BigNumber from 'bignumber.js' import { ChainId, - createERC721ContractDetailed, createERC721Token, + ERC721ContractDetailed, ERC721TokenDetailed, EthereumTokenType, isSameAddress, @@ -33,12 +33,15 @@ async function fetchAsset(url: string, chainId: ChainId) { export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts - const params = new URLSearchParams() - params.append('owner', from.toLowerCase()) - params.append('limit', String(size)) - params.append('offset', String(size * page)) - const asset = await fetchAsset(`${OpenSea_API_URL}/api/v1/assets?${params.toString()}`, chainId) + const asset = await fetchAsset( + urlcat(`${OpenSea_API_URL}/api/v1/assets?owner=:from&limit=:limit&offset=:offset}`, { + from, + offset: opts.page, + limit: opts.size, + }), + chainId, + ) if (!asset) return [] return asset.assets @@ -50,21 +53,33 @@ export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page .map((asset: OpenSeaResponse) => createERC721TokenAsset(from, asset.token_id, chainId, asset)) } -function createERC721ContractDetailedFromAssetContract( +function createERC721ContractDetailedAsset( address: string, chainId: ChainId, assetContract?: OpenSeaAssetContract, -) { - return createERC721ContractDetailed(chainId, address, assetContract?.name ?? 'unknown name', '') +): ERC721ContractDetailed { + return { + address, + chainId, + name: assetContract?.name ?? 'Unknown Token', + symbol: assetContract?.token_symbol ?? 'UNKNOWN', + baseURI: assetContract?.image_url, + type: EthereumTokenType.ERC721, + } } -function createERC721TokenAsset(address: string, tokenId: string, chainId: ChainId, asset?: OpenSeaResponse) { +function createERC721TokenAsset( + address: string, + tokenId: string, + chainId: ChainId, + asset?: OpenSeaResponse, +): ERC721TokenDetailed { return createERC721Token( - createERC721ContractDetailed(chainId, address, asset?.name ?? 'unknown name', ''), + createERC721ContractDetailedAsset(address, chainId, asset?.asset_contract), { name: asset?.name ?? asset?.asset_contract.name ?? 'unknown name', description: asset?.description ?? '', - image: asset?.image_url_original ?? asset?.image_url ?? asset?.image_preview_url ?? '', + mediaUrl: asset?.image_url_original ?? asset?.image_url ?? asset?.image_preview_url ?? '', owner: asset?.owner.address ?? '', }, tokenId, @@ -79,10 +94,10 @@ async function _getAsset(address: string, tokenId: string, chainId: ChainId) { return asset } -export async function getContract(address: string, chainId = ChainId.Mainnet) { +export async function getContract(address: string, chainId: ChainId) { const assetContract = await fetchAsset(`${OpenSea_API_URL}/api/v1/asset_contract/${address}`, chainId) - return createERC721ContractDetailedFromAssetContract(address, chainId, assetContract) + return createERC721ContractDetailedAsset(address, chainId, assetContract) } export async function getNFT(address: string, tokenId: string, chainId: ChainId) { @@ -104,10 +119,10 @@ export async function getNFTs(from: string, chainId: ChainId) { return tokens } -export async function getContractBalance(address: string, contract_address: string, chainId: ChainId) { +export async function getContractBalance(address: string, contractAddress: string, chainId: ChainId) { const assets = await getNFTs(address, chainId) - return assets.filter((x) => isSameAddress(x.contractDetailed.address, contract_address)).length + return assets.filter((x) => isSameAddress(x.contractDetailed.address, contractAddress)).length } function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { @@ -122,7 +137,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { return { is_verified: ['approved', 'verified'].includes(asset.collection?.safelist_request_status ?? ''), // it's an IOS string as my inspection - is_auction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), + isAuction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), image_url: asset.image_url_original ?? asset.image_url ?? asset.image_preview_url ?? '', asset_contract: { name: asset.asset_contract.name, @@ -183,19 +198,22 @@ export async function getAsset(address: string, tokenId: string, chainId: ChainI } export async function getHistory(address: string, tokenId: string, chainId: ChainId) { - const params = new URLSearchParams() - params.append('asset_contract_address', address) - params.append('token_id', tokenId) - params.append('offset', '0') - params.append('limit', '100') - const fetchResponse = await ( - await fetch(`${OpenSea_API_URL}/api/v1/events?${params.toString()}`, { - mode: 'cors', - headers: { - 'x-api-key': OPENSEA_API_KEY, + await fetch( + urlcat( + `${OpenSea_API_URL}/api/v1/events?asset_contract_address=:address&token_id=:tokenId&offset=0&limit=100`, + { + address, + tokenId, + }, + ), + { + mode: 'cors', + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, }, - }) + ) ).json() const { asset_events }: { asset_events: OpenSeaAssetEvent[] } = fetchResponse @@ -263,20 +281,26 @@ async function fetchOrder( size: number, chainId: ChainId, ) { - const params = new URLSearchParams() - params.append('asset_contract_address', address) - params.append('token_id', tokenId) - params.append('side', side.toString()) - params.append('offset', page.toString()) - params.append('limit', size.toString()) - const response = await fetch(`${OpenSea_API_URL}/wyvern/v1/orders?${params.toString()}`, { - method: 'GET', - mode: 'cors', - headers: { - Accept: 'application/json', - 'x-api-key': OPENSEA_API_KEY, + const response = await fetch( + urlcat( + `${OpenSea_API_URL}/wyvern/v1/orders?asset_contract_address=:address&token_id=:tokenId&side=:side&offset=:offset&limit=:limit`, + { + address, + tokenId, + size, + offset: page, + limit: size, + }, + ), + { + method: 'GET', + mode: 'cors', + headers: { + Accept: 'application/json', + 'x-api-key': OPENSEA_API_KEY, + }, }, - }) + ) if (!response.ok) return [] const { orders = [] }: { orders: AssetOrder[] } = await response.json() @@ -300,16 +324,18 @@ export async function getOrder(address: string, tokenId: string, side: OrderSide } export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const params = new URLSearchParams() - params.append('offset', `${opts.page ?? '0'}`) - params.append('limit', `${opts.size ?? '50'}`) - const { collections }: { collections: OpenSeaCollection[] } = await ( - await fetch(`${OpenSea_API_URL}/api/v1/collections?${params.toString()}`, { - headers: { - 'x-api-key': OPENSEA_API_KEY, + await fetch( + urlcat(`${OpenSea_API_URL}/api/v1/collections?offset=:offset&limit=:limit`, { + offset: opts.page, + limit: opts.size, + }), + { + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, }, - }) + ) ).json() return collections.map((x) => ({ diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 0c3915c60c5b..cffcfcab3c70 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -219,7 +219,7 @@ function createERC721TokenAsset( info: { name: asset?.meta.name ?? '', description: asset?.meta.description ?? '', - image: toRaribleImage(asset?.meta.image.url.ORIGINAL ?? asset?.meta.image.url.PREVIEW ?? ''), + mediaUrl: toRaribleImage(asset?.meta.image.url.ORIGINAL ?? asset?.meta.image.url.PREVIEW ?? ''), owner: asset?.owners[0], }, tokenId: tokenId, @@ -231,7 +231,7 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { const creator = first(asset?.creators) return { is_verified: false, - is_auction: false, + isAuction: false, token_address: asset.contract, image_url: toRaribleImage(asset?.meta.image.url.ORIGINAL), asset_contract: null, @@ -314,7 +314,7 @@ export async function getNFTs(from: string, chainId: ChainId) { params.append('owner', from) const assetResponse = await fetchFromRarible<{ total: number; items: RaribleNFTItemMapResponse[] }>( RaribleChainURL, - `/v0.1/nft/items/byOwner?${params.toString()}`, + urlcat(`/v0.1/nft/items/byOwner?owner=:from}`, { from }), { method: 'GET', mode: 'cors', @@ -326,21 +326,21 @@ export async function getNFTs(from: string, chainId: ChainId) { return assetResponse.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) } -export async function getContractBalance(from: string, contract_address: string, chainId: ChainId) { +export async function getContractBalance(from: string, contractAddress: string, chainId: ChainId) { const assets = await getNFTs(from, chainId) - return assets.filter((asset) => isSameAddress(asset.contractDetailed.address, contract_address)).length + return assets.filter((asset) => isSameAddress(asset.contractDetailed.address, contractAddress)).length } export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const params = new URLSearchParams() - - params.append('owner', from) - params.append('size', (opts.size ?? '0').toString()) const asset = await fetchFromRarible<{ total: number continuation: string items: RaribleNFTItemMapResponse[] - }>(RaribleMainnetAPI_URL, `/ethereum/nft/items/byOwner?${params.toString()}`, {}) + }>( + RaribleMainnetAPI_URL, + urlcat(`/ethereum/nft/items/byOwner?owner=:from&size=:size}`, { from, size: opts.size }), + {}, + ) if (!asset) return [] as ERC721TokenDetailed[] return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts index 7dcf46aa7625..ad049d90e736 100644 --- a/packages/web3-providers/src/types.ts +++ b/packages/web3-providers/src/types.ts @@ -71,7 +71,7 @@ export interface AssetCollection { export interface NFTAsset { is_verified: boolean collection: AssetCollection | null - is_auction: boolean + isAuction: boolean image_url: string asset_contract: { name: string; description: string; schemaName: string } | null current_price: number | null diff --git a/packages/web3-shared/base/utils/number.ts b/packages/web3-shared/base/utils/number.ts index acd76a62fdf1..b3b7b9570dc2 100644 --- a/packages/web3-shared/base/utils/number.ts +++ b/packages/web3-shared/base/utils/number.ts @@ -52,3 +52,8 @@ export function rightShift(n: BigNumber.Value, m: number | undefined | null) { export function leftShift(n: BigNumber.Value, m: number | undefined | null) { return new BigNumber(n).shiftedBy(-(m ?? 0)) } + +/** a / b */ +export function dividedBy(a: BigNumber.Value, b: BigNumber.Value) { + return new BigNumber(a).dividedBy(b) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac4a55fbae93..99f5bfebb1d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -866,6 +866,7 @@ importers: packages/web3-providers: specifiers: '@masknet/plugin-infra': workspace:* + '@masknet/web3-shared-base': workspace:* '@masknet/web3-shared-evm': workspace:* bignumber.js: ^9.0.1 date-fns: ^2.27.0 @@ -874,6 +875,7 @@ importers: urlcat: ^2.0.4 dependencies: '@masknet/plugin-infra': link:../plugin-infra + '@masknet/web3-shared-base': link:../web3-shared/base '@masknet/web3-shared-evm': link:../web3-shared/evm bignumber.js: 9.0.1 date-fns: 2.27.0 From c089e37e20a2470f15a98a79b3ec6e92e479134e Mon Sep 17 00:00:00 2001 From: lelenei Date: Wed, 15 Dec 2021 21:35:37 +0800 Subject: [PATCH 06/24] fix: reply review --- .../src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 37d62f933059..53ae060de7eb 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -10,7 +10,7 @@ import { Typography, Link, } from '@mui/material' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' +import { makeStyles } from '@masknet/theme' import { first } from 'lodash-unified' import BigNumber from 'bignumber.js' import { @@ -77,11 +77,10 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { const isAuction = asset?.value?.isAuction ?? false const isVerified = asset?.value?.is_verified ?? false const leastPrice = - asset?.value && asset.value.orders?.length ? new BigNumber(asset.value.orders[0].base_price ?? '0') : ZERO + asset?.value && asset.value.desktopOrder ? new BigNumber(asset.value.desktopOrder.current_price ?? '0') : ZERO const paymentTokens = (isAuction ? asset?.value?.offer_payment_tokens : asset?.value?.order_payment_tokens) ?? [] const selectedPaymentToken = first(paymentTokens) - const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() From a93b79a1d3e0d5d38341a09d3a5e9ecb872d1c7a Mon Sep 17 00:00:00 2001 From: lelenei Date: Wed, 15 Dec 2021 23:15:45 +0800 Subject: [PATCH 07/24] fix: reply review --- .../mask/src/plugins/Avatar/utils/index.ts | 6 +- .../Collectible/SNSAdaptor/ActionBar.tsx | 6 +- .../Collectible/SNSAdaptor/CheckoutDialog.tsx | 28 ++-- .../Collectible/SNSAdaptor/HistoryTab/Row.tsx | 1 + .../SNSAdaptor/HistoryTab/index.tsx | 10 +- .../SNSAdaptor/ListingByHighestBidCard.tsx | 6 +- .../SNSAdaptor/ListingByPriceCard.tsx | 6 +- .../Collectible/SNSAdaptor/ListingTab.tsx | 8 +- .../SNSAdaptor/MakeOfferDialog.tsx | 5 +- .../Collectible/SNSAdaptor/OfferTab.tsx | 10 +- .../Collectible/SNSAdaptor/OrderRow.tsx | 8 +- .../plugins/Collectible/SNSAdaptor/index.tsx | 8 +- .../Collectible/apis/openseaFromSDK.ts | 3 +- .../mask/src/plugins/Collectible/helpers.ts | 4 +- .../Collectible/hooks/useCollectibleState.ts | 9 ++ packages/mask/src/plugins/EVM/hooks/index.ts | 1 - .../mask/src/plugins/EVM/hooks/useAsset.ts | 5 +- .../mask/src/plugins/EVM/hooks/useHistory.ts | 24 +++- .../src/plugins/EVM/hooks/useNFTBalance.ts | 10 +- .../mask/src/plugins/EVM/hooks/useNFTs.ts | 9 -- .../mask/src/plugins/EVM/hooks/useOrders.ts | 25 +++- .../plugins/EVM/services/Asset/getAsset.ts | 20 +-- .../EVM/services/Asset/getCollections.ts | 7 +- .../plugins/EVM/services/Asset/getHistory.ts | 19 ++- .../plugins/EVM/services/Asset/getListings.ts | 17 +-- .../plugins/EVM/services/Asset/getOrder.ts | 29 ---- .../plugins/EVM/services/Asset/getOrders.ts | 26 ++++ .../src/plugins/EVM/services/Asset/index.ts | 2 +- .../src/plugins/EVM/services/NFT/getNFT.ts | 20 +-- .../plugins/EVM/services/NFT/getNFTBalance.ts | 23 +-- .../EVM/services/NFT/getNFTContract.ts | 12 +- .../src/plugins/EVM/services/NFT/getNFTs.ts | 25 ---- .../plugins/EVM/services/NFT/getNFTsPaged.ts | 17 +-- .../src/plugins/EVM/services/NFT/index.ts | 1 - .../SNSAdaptor/SelectNftContractDialog.tsx | 4 +- .../src/web3/UI/ERC721ContractSelectPanel.tsx | 8 +- packages/web3-providers/src/NFTScan/index.ts | 100 ++++++------- .../web3-providers/src/opensea/constants.ts | 1 - packages/web3-providers/src/opensea/index.ts | 135 +++++++----------- packages/web3-providers/src/rarible/index.ts | 41 ++---- packages/web3-providers/src/types.ts | 8 +- packages/web3-providers/src/utils/index.ts | 4 +- 42 files changed, 294 insertions(+), 417 deletions(-) delete mode 100644 packages/mask/src/plugins/EVM/hooks/useNFTs.ts delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getOrder.ts create mode 100644 packages/mask/src/plugins/EVM/services/Asset/getOrders.ts delete mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts diff --git a/packages/mask/src/plugins/Avatar/utils/index.ts b/packages/mask/src/plugins/Avatar/utils/index.ts index c0943c106a25..07b83e21760c 100644 --- a/packages/mask/src/plugins/Avatar/utils/index.ts +++ b/packages/mask/src/plugins/Avatar/utils/index.ts @@ -1,12 +1,12 @@ import BigNumber from 'bignumber.js' import { isNull } from 'lodash-unified' -import { ChainId } from '@masknet/web3-shared-evm' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../../EVM/messages' import Services from '../../../extension/service' import { getOrderUnitPrice } from '@masknet/web3-providers' export async function getNFT(address: string, tokenId: string) { - const asset = await EVM_RPC.getAsset(address, tokenId, ChainId.Mainnet) + const asset = await EVM_RPC.getAsset(address, tokenId, ChainId.Mainnet, NonFungibleAssetProvider.OPENSEA) return { amount: new BigNumber( @@ -24,7 +24,7 @@ export async function getNFT(address: string, tokenId: string) { } export async function createNFT(address: string, tokenId: string) { - return EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet) + return EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet, NonFungibleAssetProvider.OPENSEA) } export async function getImage(image: string): Promise { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx index d0e120af866d..64282828b8e7 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx @@ -46,7 +46,7 @@ export function ActionBar(props: ActionBarProps) { return ( - {!asset.value.isOwner && asset.value.isAuction ? ( + {!asset.value.isOwner && asset.value.is_auction ? ( ) : null} - {!asset.value.isOwner && !asset.value.isAuction && asset.value?.desktopOrder ? ( + {!asset.value.isOwner && !asset.value.is_auction && asset.value?.desktopOrder ? ( ) : null} - {!asset.value.isOwner && !asset.value.isAuction ? ( + {!asset.value.isOwner && !asset.value.is_auction ? ( diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx index 690b84cbd89e..0ceb28e09c53 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx @@ -44,13 +44,13 @@ export function HistoryTab(props: HistoryTabProps) { //#region If there is a different asset, the unit price and quantity should be displayed const isDifferenceToken = useMemo(() => { if (provider === NonFungibleAssetProvider.OPENSEA) - return events.value?.some((item) => item.price?.asset?.asset_contract.symbol !== 'ETH') + return events.value?.data.some((item) => item.price?.asset?.asset_contract.symbol !== 'ETH') else return false }, [events.value, provider]) //#endregion if (events.loading) return - if (!events.value || events.error || !events.value?.length) + if (!events.value || !events.value?.data || events.error || !events.value?.data.length) return (
@@ -91,16 +91,16 @@ export function HistoryTab(props: HistoryTabProps) { - {events.value.map((order) => ( + {events.value.data.map((order) => ( ))} - {(provider === NonFungibleAssetProvider.OPENSEA && events.value.length) || eventPage > 0 ? ( + {(provider === NonFungibleAssetProvider.OPENSEA && events.value.data.length) || eventPage > 0 ? ( setEventPage((prev) => prev - 1)} handleNextClick={() => setEventPage((prev) => prev + 1)} prevDisabled={eventPage === 0} - nextDisabled={false} + nextDisabled={!events.value.hasNextPage} page={eventPage} pageCount={10} /> diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx index 5f39a1efcaa3..de6072266ccc 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Card, CardActions, CardContent } from '@mui/material' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' +import { makeStyles } from '@masknet/theme' import { EthereumTokenType, FungibleTokenDetailed, @@ -19,6 +19,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' +import type { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => ({ footer: { @@ -49,7 +50,6 @@ export interface ListingByHighestBidCardProps { export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { const { asset, tokenWatched, paymentTokens, open, onClose } = props const { amount, token, balance, setAmount, setToken } = tokenWatched - const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -75,7 +75,7 @@ export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName, + schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx index d932e84f804c..ea9a19885147 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx @@ -1,7 +1,7 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { EthereumAddress } from 'wallet.ts' import { Box, Card, CardActions, CardContent, Checkbox, FormControlLabel, TextField, Typography } from '@mui/material' -import { makeStyles, useCustomSnackbar } from '@masknet/theme' +import { makeStyles } from '@masknet/theme' import { EthereumTokenType, FungibleTokenDetailed, @@ -21,6 +21,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' +import type { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => { return { @@ -59,7 +60,6 @@ export interface ListingByPriceCardProps { export function ListingByPriceCard(props: ListingByPriceCardProps) { const { asset, tokenWatched, paymentTokens, open, onClose } = props const { amount, token, balance, setAmount, setToken } = tokenWatched - const { showSnackbar } = useCustomSnackbar() const { t } = useI18N() const { classes } = useStyles() @@ -105,7 +105,7 @@ export function ListingByPriceCard(props: ListingByPriceCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName, + schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx index 594dd825c3af..51f936453f90 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingTab.tsx @@ -45,12 +45,12 @@ export function ListingTab() { const isDifferenceToken = useMemo(() => { if (provider === NonFungibleAssetProvider.OPENSEA) { return ( - orders.value?.some( + orders.value?.data.some( (item) => (item.payment_token_contract?.symbol !== 'WETH' && item.payment_token_contract?.symbol !== 'ETH') || (item.quantity && new BigNumber(item.quantity).toString() !== '1'), - ) && orders.value?.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 + ) && orders.value?.data.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 ) } else { return false @@ -58,8 +58,8 @@ export function ListingTab() { }, [provider, orders]) const dataSource = useMemo(() => { - if (!orders.value || !orders.value?.length) return [] - return orders.value.sort((a, b) => { + if (!orders.value || !orders.value?.data.length) return [] + return orders.value.data.sort((a, b) => { const current = new BigNumber(a.current_price ?? 0) const next = new BigNumber(b.current_price ?? 0) if (current.isLessThan(next)) return -1 diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 53ae060de7eb..84b523d7cbc2 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -37,6 +37,7 @@ import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' import { rightShift, ZERO } from '@masknet/web3-shared-base/utils/number' import type { Coin } from '../../Trader/types' +import type { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => { return { @@ -74,7 +75,7 @@ export interface MakeOfferDialogProps { export function MakeOfferDialog(props: MakeOfferDialogProps) { const { asset, open, onClose } = props - const isAuction = asset?.value?.isAuction ?? false + const isAuction = asset?.value?.is_auction ?? false const isVerified = asset?.value?.is_verified ?? false const leastPrice = asset?.value && asset.value.desktopOrder ? new BigNumber(asset.value.desktopOrder.current_price ?? '0') : ZERO @@ -101,7 +102,7 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName, + schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx index 9f02f9bab19c..02ceca99b9ef 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OfferTab.tsx @@ -44,12 +44,12 @@ export function OfferTab() { const isDifferenceToken = useMemo(() => { if (provider === NonFungibleAssetProvider.OPENSEA) { return ( - offers.value?.some( + offers.value?.data.some( (item) => (item.payment_token_contract?.symbol !== 'WETH' && item.payment_token_contract?.symbol !== 'ETH') || (item.quantity && new BigNumber(item.quantity).toString() !== '1'), - ) && offers.value.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 + ) && offers.value.data.filter((item) => isZero(item.expiration_time ?? 0)).length === 0 ) } else { return false @@ -57,12 +57,12 @@ export function OfferTab() { }, [provider, offers]) const dataSource = useMemo(() => { - if (!offers.value?.length) return [] - return offers.value + if (!offers.value?.data.length) return [] + return offers.value.data }, [offers]) if (asset.loading) return - if (!offers.value?.length || asset.error || !dataSource.length) + if (!offers.value?.data.length || asset.error || !dataSource.length) return (
diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index 00e6a96908c4..22e554e3d64a 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -9,13 +9,13 @@ import { NonFungibleAssetProvider, resolveAddressLinkOnExplorer, } from '@masknet/web3-shared-evm' -import { isZero, multipliedBy } from '@masknet/web3-shared-base' +import { isZero } from '@masknet/web3-shared-base' import { CollectibleState } from '../hooks/useCollectibleState' import { Account } from './Account' import { FormattedBalance } from '@masknet/shared' import { getOrderUnitPrice } from '@masknet/web3-providers' import type { AssetOrder } from '@masknet/web3-providers' -import fromUnixTime from 'date-fns/fromUnixTime' +import formatDistanceToNow from 'date-fns/formatDistanceToNow' const useStyles = makeStyles()((theme) => { return { @@ -156,7 +156,9 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { {order.expiration_time && !isZero(order.expiration_time) && - fromUnixTime(multipliedBy(order.expiration_time, 1000).toNumber())} + formatDistanceToNow(new Date(order.expiration_time * 1000), { + addSuffix: true, + })} ) : null} diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx index a0d539046b53..37066975847a 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx @@ -12,18 +12,16 @@ const sns: Plugin.SNSAdaptor.Definition = { ...base, init(signal) {}, PostInspector: function Component() { - const links = usePostInfoDetails - .postMetadataMentionedLinks() - .concat(usePostInfoDetails.postMentionedLinks()) as string[] + const links = usePostInfoDetails.postMetadataMentionedLinks().concat(usePostInfoDetails.postMentionedLinks()) const link = uniq(links).find(checkUrl) const asset = getAssetInfoFromURL(link) - return asset ? renderPostInspector(asset as CollectibleJSON_Payload) : null + return asset ? renderPostInspector(asset) : null }, DecryptedInspector: function Component(props) { const collectibleUrl = getRelevantUrl(getTypedMessageContent(props.message)) const asset = getAssetInfoFromURL(collectibleUrl) - return asset ? renderPostInspector(asset as CollectibleJSON_Payload) : null + return asset ? renderPostInspector(asset) : null }, } diff --git a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts index 8842f0b3ba4c..771853f380f1 100644 --- a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts +++ b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts @@ -34,8 +34,7 @@ async function createOpenSeaPort(chainId?: ChainId) { export async function getAssetFromSDK(tokenAddress: string, tokenId: string, chainId?: ChainId) { const _chainId = chainId ?? currentChainIdSettings.value - const sdkResponse = await (await createOpenSeaPort(_chainId)).api.getAsset({ tokenAddress, tokenId }) - return sdkResponse + return (await createOpenSeaPort(_chainId)).api.getAsset({ tokenAddress, tokenId }) } export async function createBuyOrder(payload: Parameters[0]) { diff --git a/packages/mask/src/plugins/Collectible/helpers.ts b/packages/mask/src/plugins/Collectible/helpers.ts index 820beff10440..1bdaf9b39912 100644 --- a/packages/mask/src/plugins/Collectible/helpers.ts +++ b/packages/mask/src/plugins/Collectible/helpers.ts @@ -13,11 +13,11 @@ export const CollectibleMetadataReader = createTypedMessageMetadataReader([]) const events = useHistory( + currentCollectibleProviderSettings.value, + eventPage, + 50, tabIndex === CollectibleTab.HISTORY ? token?.contractAddress : undefined, tabIndex === CollectibleTab.HISTORY ? token?.tokenId : undefined, ) diff --git a/packages/mask/src/plugins/EVM/hooks/index.ts b/packages/mask/src/plugins/EVM/hooks/index.ts index 40a30184a06a..e494313e72d3 100644 --- a/packages/mask/src/plugins/EVM/hooks/index.ts +++ b/packages/mask/src/plugins/EVM/hooks/index.ts @@ -1,6 +1,5 @@ export * from './useAsset' export * from './useNFTBalance' -export * from './useNFTs' export * from './useHistory' export * from './useOrders' export * from './useNetwork' diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts index c231f40d391f..29f187f5a0e4 100644 --- a/packages/mask/src/plugins/EVM/hooks/useAsset.ts +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -21,10 +21,7 @@ export function useAsset(address: string, tokenId: string, provider: NonFungible ...asset, isOrderWeth: isSameAddress(asset?.desktopOrder?.payment_token ?? '', WNATIVE_ADDRESS) ?? false, isCollectionWeth: asset?.collection?.payment_tokens?.some(currySameAddress(WNATIVE_ADDRESS)) ?? false, - isOwner: - asset?.top_ownerships.some((item: { owner: { address: string | undefined } }) => - isSameAddress(item.owner.address, account), - ) ?? false, + isOwner: asset?.top_ownerships.some((item) => isSameAddress(item.owner.address, account)) ?? false, } }, [account, chainId, WNATIVE_ADDRESS, address, tokenId, provider]) } diff --git a/packages/mask/src/plugins/EVM/hooks/useHistory.ts b/packages/mask/src/plugins/EVM/hooks/useHistory.ts index 11fa06b3b6a7..ee5acf566ba4 100644 --- a/packages/mask/src/plugins/EVM/hooks/useHistory.ts +++ b/packages/mask/src/plugins/EVM/hooks/useHistory.ts @@ -1,9 +1,27 @@ +import type { NFTHistory } from '@masknet/web3-providers' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { useAsyncRetry } from 'react-use' +import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' import { EVM_RPC } from '../messages' -export function useHistory(address?: string, tokenId?: string) { +export function useHistory( + provider: NonFungibleAssetProvider, + page: number, + size: number, + address?: string, + tokenId?: string, +): AsyncStateRetry<{ data: NFTHistory[]; hasNextPage: boolean }> { + const chainId = useChainId() return useAsyncRetry(async () => { - if (!address || !tokenId) return - return EVM_RPC.getHistory(address, tokenId) + if (!address || !tokenId) + return { + data: [], + hasNextPage: false, + } + const histories = await EVM_RPC.getHistory(address, tokenId, chainId, provider, page, size) + return { + data: histories, + hasNextPage: histories.length === size, + } }, [address, tokenId]) } diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts index cf63616044a4..d9bad59eef72 100644 --- a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts +++ b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts @@ -1,11 +1,11 @@ import { useAsyncRetry } from 'react-use' -import { useChainId } from '@masknet/web3-shared-evm' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' -export function useNFTBalance(address: string, contractAddress?: string) { +export function useNFTBalance(address: string, disabled = false) { const chainId = useChainId() return useAsyncRetry(async () => { - if (!contractAddress) return - return EVM_RPC.getNFTBalance(address, contractAddress, chainId) - }, [address, contractAddress, chainId]) + if (!address || disabled) return null + return EVM_RPC.getNFTBalance(address, chainId, NonFungibleAssetProvider.NFTSCAN) + }, [address, disabled, chainId]) } diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTs.ts b/packages/mask/src/plugins/EVM/hooks/useNFTs.ts deleted file mode 100644 index 3ee364aa9673..000000000000 --- a/packages/mask/src/plugins/EVM/hooks/useNFTs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { EVM_RPC } from '../messages' - -export function useNFTs(account: string, disabled = false) { - return useAsyncRetry(async () => { - if (!account || disabled) return null - return EVM_RPC.getNFTs(account) - }, [account, disabled]) -} diff --git a/packages/mask/src/plugins/EVM/hooks/useOrders.ts b/packages/mask/src/plugins/EVM/hooks/useOrders.ts index 2674b335c302..aa2b23bc1348 100644 --- a/packages/mask/src/plugins/EVM/hooks/useOrders.ts +++ b/packages/mask/src/plugins/EVM/hooks/useOrders.ts @@ -1,12 +1,29 @@ +import type { AssetOrder } from '@masknet/web3-providers' import { OrderSide } from '@masknet/web3-providers' -import { useChainId } from '@masknet/web3-shared-evm' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { useAsyncRetry } from 'react-use' +import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' import { EVM_RPC } from '../messages' -export function useOrders(address?: string, tokenId?: string, side?: OrderSide) { +export function useOrders( + provider: NonFungibleAssetProvider, + page: number, + size: number, + address?: string, + tokenId?: string, + side?: OrderSide, +): AsyncStateRetry<{ data: AssetOrder[]; hasNextPage: boolean }> { const chainId = useChainId() return useAsyncRetry(async () => { - if (!address || !tokenId) return - return EVM_RPC.getOrder(address, tokenId, side ?? OrderSide.Sell, chainId) + if (!address || !tokenId) + return { + data: [], + hasNextPage: false, + } + const orders = await EVM_RPC.getOrders(address, tokenId, side ?? OrderSide.Sell, chainId, provider, page, size) + return { + data: orders, + hasNextPage: orders.length === size, + } }, [address, tokenId, side, chainId]) } diff --git a/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts b/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts index 45686464d024..474b0283a406 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts @@ -1,26 +1,16 @@ -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { unreachable } from '@dimensiondev/kit' import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' -export async function getAsset( - address: string, - tokenId: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { - let asset +export async function getAsset(address: string, tokenId: string, chainId: ChainId, provider: NonFungibleAssetProvider) { switch (provider) { case NonFungibleAssetProvider.OPENSEA: - asset = await OpenSeaApi.getAsset(address, tokenId, chainId) - return asset + return OpenSeaApi.getAsset(address, tokenId, chainId) case NonFungibleAssetProvider.NFTSCAN: - asset = await NFTScanApi.getAsset(address, tokenId, chainId) - return asset + return NFTScanApi.getAsset(address, tokenId, chainId) case NonFungibleAssetProvider.RARIBLE: - asset = await RaribleApi.getAsset(address, tokenId, chainId) - return asset + return RaribleApi.getAsset(address, tokenId, chainId) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts b/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts index f80f2114d86f..14dda78672ee 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts @@ -1,12 +1,11 @@ import { OpenSeaApi } from '@masknet/web3-providers' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { unreachable } from '@dimensiondev/kit' export async function getCollections( address: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, + chainId: ChainId, + provider: NonFungibleAssetProvider, page?: number, size?: number, ) { diff --git a/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts b/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts index dabfba2f579e..577965153e4d 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts @@ -1,5 +1,4 @@ -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { unreachable } from '@dimensiondev/kit' import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' @@ -7,20 +6,18 @@ import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' export async function getHistory( address: string, tokenId: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, + chainId: ChainId, + provider: NonFungibleAssetProvider, + page: number, + size: number, ) { - let asset switch (provider) { case NonFungibleAssetProvider.OPENSEA: - asset = await OpenSeaApi.getHistory(address, tokenId, chainId) - return asset + return OpenSeaApi.getHistory(address, tokenId, chainId, page, size) case NonFungibleAssetProvider.NFTSCAN: - asset = await NFTScanApi.getHistory(address, tokenId, chainId) - return asset + return NFTScanApi.getHistory(address, tokenId, chainId) case NonFungibleAssetProvider.RARIBLE: - asset = await RaribleApi.getHistory(address, tokenId) - return asset + return RaribleApi.getHistory(address, tokenId) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/Asset/getListings.ts b/packages/mask/src/plugins/EVM/services/Asset/getListings.ts index df8349af631f..528100e7dbf9 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/getListings.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/getListings.ts @@ -1,5 +1,4 @@ -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' import { unreachable } from '@dimensiondev/kit' @@ -7,20 +6,16 @@ import { unreachable } from '@dimensiondev/kit' export async function getListings( address: string, tokenId: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, + chainId: ChainId, + provider: NonFungibleAssetProvider, ) { - let asset switch (provider) { case NonFungibleAssetProvider.OPENSEA: - asset = await OpenSeaApi.getListings(address, tokenId, chainId) - return asset + return OpenSeaApi.getListings(address, tokenId, chainId) case NonFungibleAssetProvider.NFTSCAN: - asset = await NFTScanApi.getListings(address, tokenId, chainId) - return asset + return NFTScanApi.getListings(address, tokenId, chainId) case NonFungibleAssetProvider.RARIBLE: - asset = await RaribleApi.getListings(address, tokenId, chainId) - return asset + return RaribleApi.getListings(address, tokenId, chainId) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts b/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts deleted file mode 100644 index 3267ff1e7630..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getOrder.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' - -import { unreachable } from '@dimensiondev/kit' -import type { OrderSide } from '@masknet/web3-providers' - -export async function getOrder( - address: string, - tokenId: string, - side: OrderSide, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { - let asset - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - asset = await OpenSeaApi.getOrder(address, tokenId, side, chainId) - return asset - case NonFungibleAssetProvider.NFTSCAN: - asset = await NFTScanApi.getOrder(address, tokenId, side, chainId) - return asset - case NonFungibleAssetProvider.RARIBLE: - asset = await RaribleApi.getOrder(address, tokenId, side, chainId) - return asset - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts b/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts new file mode 100644 index 000000000000..0df7165db776 --- /dev/null +++ b/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts @@ -0,0 +1,26 @@ +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' + +import { unreachable } from '@dimensiondev/kit' +import type { OrderSide } from '@masknet/web3-providers' + +export async function getOrders( + address: string, + tokenId: string, + side: OrderSide, + chainId: ChainId, + provider: NonFungibleAssetProvider, + page: number, + size: number, +) { + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getOrders(address, tokenId, side, chainId, page, size) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getOrders(address, tokenId, side, chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getOrders(address, tokenId, side, chainId) + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/Asset/index.ts b/packages/mask/src/plugins/EVM/services/Asset/index.ts index 965a778b4262..f4cf79d2c5ec 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/index.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/index.ts @@ -1,5 +1,5 @@ export * from './getAsset' export * from './getHistory' export * from './getListings' -export * from './getOrder' +export * from './getOrders' export * from './getCollections' diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts index ee72597ce8a5..0b996d8f7ec9 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts @@ -1,25 +1,15 @@ import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' import { unreachable } from '@dimensiondev/kit' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -export async function getNFT( - address: string, - tokenId: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { - let token +export async function getNFT(address: string, tokenId: string, chainId: ChainId, provider: NonFungibleAssetProvider) { switch (provider) { case NonFungibleAssetProvider.OPENSEA: - token = await OpenSeaApi.getNFT(address, tokenId, chainId) - return token + return OpenSeaApi.getNFT(address, tokenId, chainId) case NonFungibleAssetProvider.NFTSCAN: - token = await NFTScanApi.getNFT(address, tokenId, chainId) - return token + return NFTScanApi.getNFT(address, tokenId, chainId) case NonFungibleAssetProvider.RARIBLE: - token = await RaribleApi.getNFT(address, tokenId) - return token + return RaribleApi.getNFT(address, tokenId) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts index 537ba90169bb..23db0511f3af 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts @@ -1,25 +1,14 @@ -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { NFTScanApi } from '@masknet/web3-providers' import { unreachable } from '@dimensiondev/kit' -import { currentChainIdSettings } from '../../../Wallet/settings' -export async function getNFTBalance( - address: string, - contractAddress: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { - let balance +export async function getNFTBalance(address: string, chainId: ChainId, provider: NonFungibleAssetProvider) { switch (provider) { + case NonFungibleAssetProvider.RARIBLE: case NonFungibleAssetProvider.OPENSEA: - balance = await OpenSeaApi.getContractBalance(address, contractAddress, chainId) - return balance case NonFungibleAssetProvider.NFTSCAN: - balance = await NFTScanApi.getContractBalance(address, contractAddress, chainId) - return balance - case NonFungibleAssetProvider.RARIBLE: - balance = await RaribleApi.getContractBalance(address, contractAddress, chainId) - return balance + return NFTScanApi.getContractBalance(address) + default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts index b926f439e2e7..8fa51c16357b 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts @@ -1,18 +1,12 @@ import { unreachable } from '@dimensiondev/kit' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { OpenSeaApi } from '@masknet/web3-providers' -import { currentChainIdSettings } from '../../../Wallet/settings' -export async function getNFTContract( - contractAddress: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { +export async function getNFTContract(contractAddress: string, chainId: ChainId, provider: NonFungibleAssetProvider) { switch (provider) { case NonFungibleAssetProvider.OPENSEA: case NonFungibleAssetProvider.NFTSCAN: - const assetContract = await OpenSeaApi.getContract(contractAddress, chainId) - return assetContract + return OpenSeaApi.getContract(contractAddress, chainId) case NonFungibleAssetProvider.RARIBLE: return default: diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts deleted file mode 100644 index 10ced85d1225..000000000000 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTs.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' -import { unreachable } from '@dimensiondev/kit' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' - -export async function getNFTs( - from: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, -) { - let tokens - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - tokens = await OpenSeaApi.getNFTs(from, chainId) - return tokens - case NonFungibleAssetProvider.NFTSCAN: - tokens = await NFTScanApi.getNFTs(from, chainId) - return tokens - case NonFungibleAssetProvider.RARIBLE: - tokens = await RaribleApi.getNFTs(from, chainId) - return tokens - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts index affef1f8d086..6b892e8bf8ab 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts @@ -1,27 +1,22 @@ import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' import { unreachable } from '@dimensiondev/kit' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { currentChainIdSettings } from '../../../Wallet/settings' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' export async function getNFTsPaged( from: string, - chainId = currentChainIdSettings.value, - provider = NonFungibleAssetProvider.OPENSEA, + chainId: ChainId, + provider: NonFungibleAssetProvider, page?: number, size?: number, ) { - let assets switch (provider) { case NonFungibleAssetProvider.OPENSEA: - assets = await OpenSeaApi.getNFTsPaged(from, { chainId, page, size }) - return assets + return OpenSeaApi.getNFTsPaged(from, { chainId, page, size }) case NonFungibleAssetProvider.NFTSCAN: - assets = await NFTScanApi.getNFTsPaged(from, { chainId, page, size }) - return assets + return NFTScanApi.getNFTsPaged(from, { chainId, page, size }) case NonFungibleAssetProvider.RARIBLE: - assets = await RaribleApi.getNFTsPaged(from, { chainId, page, size }) - return assets + return RaribleApi.getNFTsPaged(from, { chainId, page, size }) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/EVM/services/NFT/index.ts b/packages/mask/src/plugins/EVM/services/NFT/index.ts index 2a004d34a656..ac8b764d37f8 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/index.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/index.ts @@ -1,5 +1,4 @@ export * from './getNFTBalance' export * from './getNFT' -export * from './getNFTs' export * from './getNFTContract' export * from './getNFTsPaged' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx index 12f389e788da..2024add899ae 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx @@ -21,7 +21,7 @@ import Fuse from 'fuse.js' import { useERC721ContractDetailed } from '@masknet/web3-shared-evm' import classNames from 'classnames' import { unionBy } from 'lodash-unified' -import { useNFTs } from '../../EVM/hooks/useNFTs' +import { useNFTBalance } from '../../EVM/hooks' const useStyles = makeStyles()((theme) => ({ search: { @@ -151,7 +151,7 @@ export function SelectNftContractDialog(props: SelectNftContractDialogProps) { }, [id, setDialog]) //#endregion - const { value: assets } = useNFTs(account, !open) + const { value: assets } = useNFTBalance(account, !open) const erc721InDb = useERC721Tokens() const allContractsInDb = unionBy( diff --git a/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx b/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx index e2feb84e1ed8..19f823620c07 100644 --- a/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx +++ b/packages/mask/src/web3/UI/ERC721ContractSelectPanel.tsx @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react' import { v4 as uuid } from 'uuid' -import { ERC721ContractDetailed, useERC721ContractBalance, useAccount } from '@masknet/web3-shared-evm' +import { ERC721ContractDetailed, useERC721ContractBalance, useAccount, isSameAddress } from '@masknet/web3-shared-evm' import classNames from 'classnames' import { EthereumAddress } from 'wallet.ts' import { Box, Typography, CircularProgress } from '@mui/material' @@ -72,10 +72,14 @@ export function ERC721ContractSelectPanel(props: ERC721TokenSelectPanelProps) { const account = useAccount() const { classes } = useStyles({ hasIcon: Boolean(contract?.iconURL) }) const { value: balanceFromChain, loading: loadingFromChain } = useERC721ContractBalance(contract?.address, account) - const { value: balanceFromNFTscan, loading: loadingBalanceFromNFTscan } = useNFTBalance(account, contract?.address) + const { value: assets, loading: loadingBalanceFromNFTscan } = useNFTBalance(account, !contract) const { t } = useI18N() + const balanceFromNFTscan = assets + ? assets.find((asset) => isSameAddress(asset.contractDetailed.address, contract?.address))?.balance + : undefined + const balance = balanceFromChain ? Number(balanceFromChain) : balanceFromNFTscan ?? 0 onBalanceChange(balance) diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 6bfdb97cab85..45768d0f2237 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -1,41 +1,40 @@ import { NFTSCAN_BASE_API, NFTSCAN_ID, NFTSCAN_SECRET, NFTSCAN_URL } from './constants' import type { NFTScanAsset, NFT_Assets } from './types' -import { - createERC721ContractDetailed, - ChainId, - createERC721Token, - ERC721ContractDetailed, - EthereumTokenType, - ERC721TokenDetailed, - isSameAddress, -} from '@masknet/web3-shared-evm' +import { createERC721ContractDetailed, ChainId, createERC721Token } from '@masknet/web3-shared-evm' import type { NFTAsset, OrderSide } from '../types' +import urlcat from 'urlcat' +import addSeconds from 'date-fns/addSeconds' -let token: string = '' -let token_expiration: number = 0 +const tokenCache = new Map<'token', { token: string; expiration: number }>() async function getToken() { - const params = new URLSearchParams() - params.append('apiKey', NFTSCAN_ID) - params.append('apiSecret', NFTSCAN_SECRET) - const response = await fetch(`${NFTSCAN_URL}/gw/token?${params.toString()}`, { - mode: 'cors', - }) + const token = tokenCache.get('token') + if (token && Date.now() <= token.expiration) return token.token + + const response = await fetch( + urlcat(NFTSCAN_URL, '/gw/token', { + apiKey: NFTSCAN_ID, + apiSecret: NFTSCAN_SECRET, + }), + { + mode: 'cors', + }, + ) const { data }: { data: { accessToken: string; expiration: number } } = await response.json() - token = data.accessToken - token_expiration = Date.now() + data.expiration * 1000 + tokenCache.set('token', { + token: data.accessToken, + expiration: addSeconds(Date.now(), data.expiration).getTime(), + }) + return data.accessToken } -async function fetchAsset(path: string, config = {} as RequestInit) { - if (token === '' || Date.now() > token_expiration) { - await getToken() - } +async function fetchAsset(path: string, config: Partial = {}) { const response = await fetch(`${NFTSCAN_BASE_API}/${path}`, { method: 'POST', ...config, headers: { - 'Access-Token': token, + 'Access-Token': await getToken(), }, }) @@ -58,8 +57,8 @@ function createERC721TokenAsset(asset: NFTScanAsset) { return createERC721Token( createERC721ContractDetailedFromAssetContract(asset), { - name: json.name ?? 'unknown name', - description: json.description ?? 'unknown symbol', + name: json.name ?? '', + description: json.description ?? '', mediaUrl: json.image || '', owner: asset.nft_holder ?? '', }, @@ -67,7 +66,7 @@ function createERC721TokenAsset(asset: NFTScanAsset) { ) } -async function getContractsAndBalance(address: string) { +export async function getContractBalance(address: string) { const response = await fetchAsset('getGroupByNftContract', { body: JSON.stringify({ erc: 'erc721', @@ -80,13 +79,12 @@ async function getContractsAndBalance(address: string) { return data .map((x) => { - const contractDetailed: ERC721ContractDetailed = { - name: x.nft_platform_name, - symbol: '', - address: x.nft_contract_address, - chainId: ChainId.Mainnet, - type: EthereumTokenType.ERC721, - } + const contractDetailed = createERC721ContractDetailed( + ChainId.Mainnet, + x.nft_contract_address, + x.nft_platform_name, + ) + const balance = x.nft_asset.length return { @@ -111,21 +109,6 @@ export async function getNFT(address: string, tokenId: string, chainId: ChainId) return createERC721TokenAsset(data) } -export async function getNFTs(from: string, chainId: ChainId) { - let tokens: ERC721TokenDetailed[] = [] - let page = 0 - let assets - const size = 50 - do { - assets = await getNFTsPaged(from, { chainId, page, size }) - if (assets.length === 0) break - tokens = tokens.concat(assets) - page = page + 1 - } while (assets.length === size) - - return tokens -} - export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const { size = 50, page = 0 } = opts @@ -139,17 +122,16 @@ export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page? }) if (!response) return [] - const { data }: { data: { content: NFTScanAsset[]; page_index: number; page_size: number; total: number } } = - response - - return data.content.map((asset) => createERC721TokenAsset(asset)) -} + type ResponseData = { + content: NFTScanAsset[] + page_index: number + page_size: number + total: number + } -export async function getContractBalance(address: string, contractAddress: string, chainId: ChainId) { - const response = await getContractsAndBalance(address) - if (!response) return + const { data }: { data: ResponseData } = response - return response.find((x) => isSameAddress(x.contractDetailed.address, contractAddress))?.balance + return data.content.map(createERC721TokenAsset) } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { @@ -164,7 +146,7 @@ export async function getListings(address: string, tokenId: string, chainId: Cha return [] } -export async function getOrder(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { +export async function getOrders(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { return [] } diff --git a/packages/web3-providers/src/opensea/constants.ts b/packages/web3-providers/src/opensea/constants.ts index 58ee01865bf1..4322fac62d5c 100644 --- a/packages/web3-providers/src/opensea/constants.ts +++ b/packages/web3-providers/src/opensea/constants.ts @@ -1,4 +1,3 @@ export const OpenSeaAccountURL = 'https://opensea.io/accounts/' export const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' -//export const OpenSea_API_URL = 'https://opensea-proxy.r2d2.to' export const OpenSea_API_URL = 'https://api.opensea.io' diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 39e79dd19688..68cd9adde724 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -2,20 +2,20 @@ import { OpenSeaAccountURL, OPENSEA_API_KEY, OpenSea_API_URL } from './constants import type { OpenSeaAssetContract, OpenSeaAssetEvent, OpenSeaResponse, OpenSeaCollection } from './types' import urlcat from 'urlcat' import { head, uniqBy } from 'lodash-unified' -import type { AssetCollection, AssetOrder, NFTAsset, NFTHistory, OrderSide } from '../types' +import type { AssetCollection, AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from '../utils' import fromUnixTime from 'date-fns/fromUnixTime' import BigNumber from 'bignumber.js' import { ChainId, + createERC721ContractDetailed, createERC721Token, ERC721ContractDetailed, ERC721TokenDetailed, EthereumTokenType, - isSameAddress, } from '@masknet/web3-shared-evm' -async function fetchAsset(url: string, chainId: ChainId) { +async function fetchAsset(url: string, chainId: ChainId) { if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return const response = await ( @@ -28,13 +28,13 @@ async function fetchAsset(url: string, chainId: ChainId) { }) ).json() - return response + return response as T } export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts - const asset = await fetchAsset( + const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>( urlcat(`${OpenSea_API_URL}/api/v1/assets?owner=:from&limit=:limit&offset=:offset}`, { from, offset: opts.page, @@ -58,14 +58,7 @@ function createERC721ContractDetailedAsset( chainId: ChainId, assetContract?: OpenSeaAssetContract, ): ERC721ContractDetailed { - return { - address, - chainId, - name: assetContract?.name ?? 'Unknown Token', - symbol: assetContract?.token_symbol ?? 'UNKNOWN', - baseURI: assetContract?.image_url, - type: EthereumTokenType.ERC721, - } + return createERC721ContractDetailed(chainId, address, assetContract?.name, assetContract?.token_symbol) } function createERC721TokenAsset( @@ -77,7 +70,7 @@ function createERC721TokenAsset( return createERC721Token( createERC721ContractDetailedAsset(address, chainId, asset?.asset_contract), { - name: asset?.name ?? asset?.asset_contract.name ?? 'unknown name', + name: asset?.name ?? asset?.asset_contract.name ?? '', description: asset?.description ?? '', mediaUrl: asset?.image_url_original ?? asset?.image_url ?? asset?.image_preview_url ?? '', owner: asset?.owner.address ?? '', @@ -87,15 +80,17 @@ function createERC721TokenAsset( } async function _getAsset(address: string, tokenId: string, chainId: ChainId) { - const asset = (await fetchAsset( + return fetchAsset( urlcat(`${OpenSea_API_URL}/api/v1/asset/:address/:tokenId`, { address, tokenId }), chainId, - )) as OpenSeaResponse - return asset + ) } export async function getContract(address: string, chainId: ChainId) { - const assetContract = await fetchAsset(`${OpenSea_API_URL}/api/v1/asset_contract/${address}`, chainId) + const assetContract = await fetchAsset( + `${OpenSea_API_URL}/api/v1/asset_contract/${address}`, + chainId, + ) return createERC721ContractDetailedAsset(address, chainId, assetContract) } @@ -105,27 +100,7 @@ export async function getNFT(address: string, tokenId: string, chainId: ChainId) return createERC721TokenAsset(address, tokenId, chainId, asset) } -export async function getNFTs(from: string, chainId: ChainId) { - let tokens: ERC721TokenDetailed[] = [] - let page = 0 - let assets - const size = 50 - do { - assets = await getNFTsPaged(from, { chainId, page, size }) - tokens = tokens.concat(assets) - page = page + 1 - } while (assets.length === size) - - return tokens -} - -export async function getContractBalance(address: string, contractAddress: string, chainId: ChainId) { - const assets = await getNFTs(address, chainId) - - return assets.filter((x) => isSameAddress(x.contractDetailed.address, contractAddress)).length -} - -function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { +function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { const desktopOrder = head( asset.sell_orders?.sort((a, b) => new BigNumber(getOrderUSDPrice(a.current_price, a.payment_token_contract?.usd_price) ?? 0) @@ -137,7 +112,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { return { is_verified: ['approved', 'verified'].includes(asset.collection?.safelist_request_status ?? ''), // it's an IOS string as my inspection - isAuction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), + is_auction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), image_url: asset.image_url_original ?? asset.image_url ?? asset.image_preview_url ?? '', asset_contract: { name: asset.asset_contract.name, @@ -173,7 +148,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { end_time: asset.endTime ? new Date(asset.endTime) : desktopOrder - ? fromUnixTime(Number.parseInt(desktopOrder.listing_time as unknown as string, 10)) + ? fromUnixTime(desktopOrder.listing_time) : null, order_payment_tokens: desktopOrder?.payment_token_contract ? [toTokenDetailed(chainId, desktopOrder.payment_token_contract)] @@ -184,10 +159,19 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId) { ).filter((x) => x.type === EthereumTokenType.ERC20), slug: asset.collection.slug, desktopOrder, - top_ownerships: asset.top_ownerships.map((x) => ({ owner: x.owner })), + top_ownerships: asset.top_ownerships.map((x) => ({ + owner: { + address: x.owner.address, + profile_img_url: x.owner.profile_img_url, + user: { + username: x.owner.user?.username ?? '', + }, + link: '', + } as NFTAssetOwner, + })), collection: asset.collection as unknown as AssetCollection, response_: asset as any, - } as NFTAsset + } } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { @@ -197,16 +181,15 @@ export async function getAsset(address: string, tokenId: string, chainId: ChainI return createNFTAsset(asset, chainId) } -export async function getHistory(address: string, tokenId: string, chainId: ChainId) { +export async function getHistory(address: string, tokenId: string, chainId: ChainId, page: number, size: number) { const fetchResponse = await ( await fetch( - urlcat( - `${OpenSea_API_URL}/api/v1/events?asset_contract_address=:address&token_id=:tokenId&offset=0&limit=100`, - { - address, - tokenId, - }, - ), + urlcat(OpenSea_API_URL, '/api/v1/events', { + asset_contract_address: address, + token_id: tokenId, + offset: page, + limit: size, + }), { mode: 'cors', headers: { @@ -218,7 +201,7 @@ export async function getHistory(address: string, tokenId: string, chainId: Chai const { asset_events }: { asset_events: OpenSeaAssetEvent[] } = fetchResponse - return asset_events.map((event) => createNFTHistory(event)) + return asset_events.map(createNFTHistory) } function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { @@ -266,32 +249,29 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { eventType: event.event_type, transactionBlockExplorerLink: event.transaction?.blockExplorerLink, timestamp: new Date(`${event.created_date}Z`).getTime(), - } as NFTHistory + } } export async function getListings(address: string, tokenId: string, chainId: ChainId) { return [] } -async function fetchOrder( +async function fetchOrders( + chainId: ChainId, address: string, tokenId: string, side: number, page: number, size: number, - chainId: ChainId, ) { const response = await fetch( - urlcat( - `${OpenSea_API_URL}/wyvern/v1/orders?asset_contract_address=:address&token_id=:tokenId&side=:side&offset=:offset&limit=:limit`, - { - address, - tokenId, - size, - offset: page, - limit: size, - }, - ), + urlcat(OpenSea_API_URL, '/wyvern/v1/orders', { + asset_contract_address: address, + token_id: tokenId, + side, + offset: page, + limit: size, + }), { method: 'GET', mode: 'cors', @@ -307,26 +287,21 @@ async function fetchOrder( return orders } -export async function getOrder(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { - let orders: AssetOrder[] = [] - - let page = 0 - const size = 50 - let order - do { - order = await fetchOrder(address, tokenId, side, page, size, chainId) - if (order.length === 0) break - orders = orders.concat(order ?? []) - page = page + 1 - } while (order.length === size) - - return orders +export async function getOrders( + address: string, + tokenId: string, + side: OrderSide, + chainId: ChainId, + page: number, + size: number, +) { + return fetchOrders(chainId, address, tokenId, side, page, size) } export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { const { collections }: { collections: OpenSeaCollection[] } = await ( await fetch( - urlcat(`${OpenSea_API_URL}/api/v1/collections?offset=:offset&limit=:limit`, { + urlcat(OpenSea_API_URL, '/api/v1/collections', { offset: opts.page, limit: opts.size, }), diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index cffcfcab3c70..2581a04cff3d 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -13,7 +13,6 @@ import { ERC721TokenDetailed, EthereumTokenType, FungibleTokenDetailed, - isSameAddress, } from '@masknet/web3-shared-evm' import { RaribleUserURL, @@ -123,7 +122,7 @@ export async function getListings(tokenAddress: string, tokenId: string, chainId }) } -export async function getOrder(tokenAddress: string, tokenId: string, side: OrderSide, chainId: ChainId) { +export async function getOrders(tokenAddress: string, tokenId: string, side: OrderSide, chainId: ChainId) { switch (side) { case OrderSide.Buy: return getOffers(tokenAddress, tokenId, chainId) @@ -226,12 +225,12 @@ function createERC721TokenAsset( } } -function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { +function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): NFTAsset { const owner = first(asset?.owners) const creator = first(asset?.creators) return { is_verified: false, - isAuction: false, + is_auction: false, token_address: asset.contract, image_url: toRaribleImage(asset?.meta.image.url.ORIGINAL), asset_contract: null, @@ -272,10 +271,12 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId) { }, }, ] - : null, + : [], slug: '', response_: asset, - } as NFTAsset + token_id: asset.tokenId, + safelist_request_status: '', + } } async function _getAsset(address: string, tokenId: string) { @@ -309,38 +310,12 @@ export async function getNFT(tokenAddress: string, tokenId: string) { return createERC721TokenAsset(tokenAddress, tokenId, asset) } -export async function getNFTs(from: string, chainId: ChainId) { - const params = new URLSearchParams() - params.append('owner', from) - const assetResponse = await fetchFromRarible<{ total: number; items: RaribleNFTItemMapResponse[] }>( - RaribleChainURL, - urlcat(`/v0.1/nft/items/byOwner?owner=:from}`, { from }), - { - method: 'GET', - mode: 'cors', - headers: { - 'content-type': 'application/json', - }, - }, - ) - return assetResponse.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) -} - -export async function getContractBalance(from: string, contractAddress: string, chainId: ChainId) { - const assets = await getNFTs(from, chainId) - return assets.filter((asset) => isSameAddress(asset.contractDetailed.address, contractAddress)).length -} - export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const asset = await fetchFromRarible<{ total: number continuation: string items: RaribleNFTItemMapResponse[] - }>( - RaribleMainnetAPI_URL, - urlcat(`/ethereum/nft/items/byOwner?owner=:from&size=:size}`, { from, size: opts.size }), - {}, - ) + }>(RaribleMainnetAPI_URL, urlcat('/ethereum/nft/items/byOwner', { owner: from, size: opts.size }), {}) if (!asset) return [] as ERC721TokenDetailed[] return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts index ad049d90e736..b7de7aea30b7 100644 --- a/packages/web3-providers/src/types.ts +++ b/packages/web3-providers/src/types.ts @@ -70,8 +70,8 @@ export interface AssetCollection { export interface NFTAsset { is_verified: boolean - collection: AssetCollection | null - isAuction: boolean + collection?: AssetCollection + is_auction: boolean image_url: string asset_contract: { name: string; description: string; schemaName: string } | null current_price: number | null @@ -85,12 +85,12 @@ export interface NFTAsset { description: string name: string collection_name: string - animation_url: string + animation_url?: string end_time: Date | null order_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] offer_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] slug: string | null - desktopOrder: AssetOrder | null + desktopOrder?: AssetOrder top_ownerships: { owner: NFTAssetOwner }[] diff --git a/packages/web3-providers/src/utils/index.ts b/packages/web3-providers/src/utils/index.ts index eb22e792c41f..1587c5279ce7 100644 --- a/packages/web3-providers/src/utils/index.ts +++ b/packages/web3-providers/src/utils/index.ts @@ -5,7 +5,7 @@ export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quan if (!currentPrice || !decimals || !quantity) return const price = formatBalance(currentPrice, decimals) const _quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) - return new BigNumber(price).dividedBy(_quantity).toFixed(4, 1).toString() + return new BigNumber(price).dividedBy(_quantity).toFixed(4, 1) } export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { @@ -13,7 +13,7 @@ export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decim const price = formatBalance(usdPrice, 0) const quantity = formatBalance(currentPrice, decimals) - return new BigNumber(price).multipliedBy(quantity).toFixed(2, 1).toString() + return new BigNumber(price).multipliedBy(quantity).toFixed(2, 1) } export function getLastSalePrice(total_price?: string, decimals?: number) { From 4bd06c845d6010ab48f8ff1bd9590589d84a5ee8 Mon Sep 17 00:00:00 2001 From: lelenei Date: Thu, 16 Dec 2021 10:50:12 +0800 Subject: [PATCH 08/24] fix: image path --- packages/web3-providers/src/rarible/helpers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web3-providers/src/rarible/helpers.ts b/packages/web3-providers/src/rarible/helpers.ts index 07bbe45ac8eb..8298b5fcde2e 100644 --- a/packages/web3-providers/src/rarible/helpers.ts +++ b/packages/web3-providers/src/rarible/helpers.ts @@ -2,5 +2,6 @@ import { RaribleIPFSURL } from './constants' export function toRaribleImage(url?: string) { if (!url) return '' - return `${RaribleIPFSURL}${url.replace('ipfs://ipfs/', '')}` + if (url.includes('ipfs://ipfs/')) return `${RaribleIPFSURL}${url.replace('ipfs://ipfs/', '')}` + return url } From c2fe85c1996729402644512c72275c48c2338cc8 Mon Sep 17 00:00:00 2001 From: lelenei Date: Fri, 17 Dec 2021 16:23:20 +0800 Subject: [PATCH 09/24] fix: reply review --- .../mask/src/plugins/Avatar/utils/index.ts | 9 +- .../SNSAdaptor/ListingByHighestBidCard.tsx | 8 +- .../SNSAdaptor/ListingByPriceCard.tsx | 8 +- .../SNSAdaptor/MakeOfferDialog.tsx | 8 +- .../mask/src/plugins/EVM/hooks/useAsset.ts | 2 +- .../mask/src/plugins/EVM/hooks/useHistory.ts | 2 +- .../src/plugins/EVM/hooks/useNFTBalance.ts | 2 +- .../mask/src/plugins/EVM/hooks/useOrders.ts | 10 +- .../plugins/EVM/services/Asset/getAsset.ts | 17 --- .../EVM/services/Asset/getCollections.ts | 22 ---- .../plugins/EVM/services/Asset/getHistory.ts | 24 ---- .../plugins/EVM/services/Asset/getListings.ts | 22 ---- .../plugins/EVM/services/Asset/getOrders.ts | 26 ---- .../src/plugins/EVM/services/Asset/index.ts | 120 +++++++++++++++++- .../src/plugins/EVM/services/NFT/getNFT.ts | 16 --- .../plugins/EVM/services/NFT/getNFTBalance.ts | 15 --- .../EVM/services/NFT/getNFTContract.ts | 15 --- .../plugins/EVM/services/NFT/getNFTsPaged.ts | 23 ---- .../src/plugins/EVM/services/NFT/index.ts | 88 ++++++++++++- .../src/plugins/Wallet/services/assets.ts | 16 ++- packages/web3-providers/src/NFTScan/index.ts | 2 +- .../web3-providers/src/opensea/constants.ts | 4 +- packages/web3-providers/src/opensea/index.ts | 34 ++--- packages/web3-providers/src/opensea/types.ts | 5 +- .../web3-providers/src/rarible/helpers.ts | 2 +- packages/web3-providers/src/rarible/index.ts | 2 +- packages/web3-shared/evm/pipes/index.ts | 2 +- 27 files changed, 274 insertions(+), 230 deletions(-) delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getAsset.ts delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getCollections.ts delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getHistory.ts delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getListings.ts delete mode 100644 packages/mask/src/plugins/EVM/services/Asset/getOrders.ts delete mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFT.ts delete mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts delete mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts delete mode 100644 packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts diff --git a/packages/mask/src/plugins/Avatar/utils/index.ts b/packages/mask/src/plugins/Avatar/utils/index.ts index 07b83e21760c..cd44dd2b865f 100644 --- a/packages/mask/src/plugins/Avatar/utils/index.ts +++ b/packages/mask/src/plugins/Avatar/utils/index.ts @@ -6,7 +6,12 @@ import Services from '../../../extension/service' import { getOrderUnitPrice } from '@masknet/web3-providers' export async function getNFT(address: string, tokenId: string) { - const asset = await EVM_RPC.getAsset(address, tokenId, ChainId.Mainnet, NonFungibleAssetProvider.OPENSEA) + const asset = await EVM_RPC.getAsset({ + address, + tokenId, + chainId: ChainId.Mainnet, + provider: NonFungibleAssetProvider.OPENSEA, + }) return { amount: new BigNumber( @@ -24,7 +29,7 @@ export async function getNFT(address: string, tokenId: string) { } export async function createNFT(address: string, tokenId: string) { - return EVM_RPC.getNFT(address, tokenId, ChainId.Mainnet, NonFungibleAssetProvider.OPENSEA) + return EVM_RPC.getNFT({ address, tokenId, chainId: ChainId.Mainnet, provider: NonFungibleAssetProvider.OPENSEA }) } export async function getImage(image: string): Promise { diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx index de6072266ccc..d56c7d87363b 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx @@ -19,7 +19,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' -import type { WyvernSchemaName } from 'opensea-js/lib/types' +import { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => ({ footer: { @@ -75,7 +75,11 @@ export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, + schemaName: Object.values(WyvernSchemaName).includes( + asset.value.asset_contract?.schemaName as WyvernSchemaName, + ) + ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) + : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx index ea9a19885147..5a13e7d06729 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx @@ -21,7 +21,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' -import type { WyvernSchemaName } from 'opensea-js/lib/types' +import { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => { return { @@ -105,7 +105,11 @@ export function ListingByPriceCard(props: ListingByPriceCardProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, + schemaName: Object.values(WyvernSchemaName).includes( + asset.value.asset_contract?.schemaName as WyvernSchemaName, + ) + ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) + : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 84b523d7cbc2..e16478a0add7 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -37,7 +37,7 @@ import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' import { rightShift, ZERO } from '@masknet/web3-shared-base/utils/number' import type { Coin } from '../../Trader/types' -import type { WyvernSchemaName } from 'opensea-js/lib/types' +import { WyvernSchemaName } from 'opensea-js/lib/types' const useStyles = makeStyles()((theme) => { return { @@ -102,7 +102,11 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: asset.value.asset_contract?.schemaName as WyvernSchemaName, + schemaName: Object.values(WyvernSchemaName).includes( + asset.value.asset_contract?.schemaName as WyvernSchemaName, + ) + ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) + : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/EVM/hooks/useAsset.ts b/packages/mask/src/plugins/EVM/hooks/useAsset.ts index 29f187f5a0e4..9d621953bce8 100644 --- a/packages/mask/src/plugins/EVM/hooks/useAsset.ts +++ b/packages/mask/src/plugins/EVM/hooks/useAsset.ts @@ -15,7 +15,7 @@ export function useAsset(address: string, tokenId: string, provider: NonFungible const { WNATIVE_ADDRESS } = useTokenConstants() return useAsyncRetry(async () => { - const asset = await EVM_RPC.getAsset(address, tokenId, chainId, provider) + const asset = await EVM_RPC.getAsset({ address, tokenId, chainId, provider }) return { ...asset, diff --git a/packages/mask/src/plugins/EVM/hooks/useHistory.ts b/packages/mask/src/plugins/EVM/hooks/useHistory.ts index ee5acf566ba4..9316c2a129e7 100644 --- a/packages/mask/src/plugins/EVM/hooks/useHistory.ts +++ b/packages/mask/src/plugins/EVM/hooks/useHistory.ts @@ -18,7 +18,7 @@ export function useHistory( data: [], hasNextPage: false, } - const histories = await EVM_RPC.getHistory(address, tokenId, chainId, provider, page, size) + const histories = await EVM_RPC.getHistory({ address, tokenId, chainId, provider, page, size }) return { data: histories, hasNextPage: histories.length === size, diff --git a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts index d9bad59eef72..c388b1a0bf15 100644 --- a/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts +++ b/packages/mask/src/plugins/EVM/hooks/useNFTBalance.ts @@ -6,6 +6,6 @@ export function useNFTBalance(address: string, disabled = false) { const chainId = useChainId() return useAsyncRetry(async () => { if (!address || disabled) return null - return EVM_RPC.getNFTBalance(address, chainId, NonFungibleAssetProvider.NFTSCAN) + return EVM_RPC.getNFTBalance({ address, chainId, provider: NonFungibleAssetProvider.NFTSCAN }) }, [address, disabled, chainId]) } diff --git a/packages/mask/src/plugins/EVM/hooks/useOrders.ts b/packages/mask/src/plugins/EVM/hooks/useOrders.ts index aa2b23bc1348..e2fbbdd7a840 100644 --- a/packages/mask/src/plugins/EVM/hooks/useOrders.ts +++ b/packages/mask/src/plugins/EVM/hooks/useOrders.ts @@ -20,7 +20,15 @@ export function useOrders( data: [], hasNextPage: false, } - const orders = await EVM_RPC.getOrders(address, tokenId, side ?? OrderSide.Sell, chainId, provider, page, size) + const orders = await EVM_RPC.getOrders({ + address, + tokenId, + side: side ?? OrderSide.Sell, + chainId, + provider, + page, + size, + }) return { data: orders, hasNextPage: orders.length === size, diff --git a/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts b/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts deleted file mode 100644 index 474b0283a406..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getAsset.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' - -import { unreachable } from '@dimensiondev/kit' -import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' - -export async function getAsset(address: string, tokenId: string, chainId: ChainId, provider: NonFungibleAssetProvider) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getAsset(address, tokenId, chainId) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getAsset(address, tokenId, chainId) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getAsset(address, tokenId, chainId) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts b/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts deleted file mode 100644 index 14dda78672ee..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getCollections.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { OpenSeaApi } from '@masknet/web3-providers' -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { unreachable } from '@dimensiondev/kit' - -export async function getCollections( - address: string, - chainId: ChainId, - provider: NonFungibleAssetProvider, - page?: number, - size?: number, -) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getCollections(address, { chainId, page, size }) - case NonFungibleAssetProvider.RARIBLE: - return [] - case NonFungibleAssetProvider.NFTSCAN: - return [] - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts b/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts deleted file mode 100644 index 577965153e4d..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getHistory.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' - -import { unreachable } from '@dimensiondev/kit' -import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' - -export async function getHistory( - address: string, - tokenId: string, - chainId: ChainId, - provider: NonFungibleAssetProvider, - page: number, - size: number, -) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getHistory(address, tokenId, chainId, page, size) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getHistory(address, tokenId, chainId) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getHistory(address, tokenId) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getListings.ts b/packages/mask/src/plugins/EVM/services/Asset/getListings.ts deleted file mode 100644 index 528100e7dbf9..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getListings.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' - -import { unreachable } from '@dimensiondev/kit' - -export async function getListings( - address: string, - tokenId: string, - chainId: ChainId, - provider: NonFungibleAssetProvider, -) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getListings(address, tokenId, chainId) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getListings(address, tokenId, chainId) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getListings(address, tokenId, chainId) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts b/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts deleted file mode 100644 index 0df7165db776..000000000000 --- a/packages/mask/src/plugins/EVM/services/Asset/getOrders.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' - -import { unreachable } from '@dimensiondev/kit' -import type { OrderSide } from '@masknet/web3-providers' - -export async function getOrders( - address: string, - tokenId: string, - side: OrderSide, - chainId: ChainId, - provider: NonFungibleAssetProvider, - page: number, - size: number, -) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getOrders(address, tokenId, side, chainId, page, size) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getOrders(address, tokenId, side, chainId) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getOrders(address, tokenId, side, chainId) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/Asset/index.ts b/packages/mask/src/plugins/EVM/services/Asset/index.ts index f4cf79d2c5ec..10ba11903524 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/index.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/index.ts @@ -1,5 +1,115 @@ -export * from './getAsset' -export * from './getHistory' -export * from './getListings' -export * from './getOrders' -export * from './getCollections' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' + +import { unreachable } from '@dimensiondev/kit' +import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' +import type { OrderSide } from '@masknet/web3-providers' + +export interface AssetOption { + address: string + tokenId: string + chainId: ChainId + provider: NonFungibleAssetProvider +} + +export interface OrderOption { + address: string + tokenId: string + side: OrderSide + chainId: ChainId + provider: NonFungibleAssetProvider + page: number + size: number +} + +export interface ListOption { + address: string + tokenId: string + chainId: ChainId + provider: NonFungibleAssetProvider +} + +export interface HistoryOtpion { + address: string + tokenId: string + chainId: ChainId + provider: NonFungibleAssetProvider + page: number + size: number +} + +export interface CollectionOption { + address: string + chainId: ChainId + provider: NonFungibleAssetProvider + page: number + size: number +} + +export async function getAsset(options: AssetOption) { + switch (options.provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getAsset(options.address, options.tokenId, options.chainId) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getAsset(options.address, options.tokenId, options.chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getAsset(options.address, options.tokenId, options.chainId) + default: + unreachable(options.provider) + } +} + +export async function getOrders(options: OrderOption) { + const { provider, address, tokenId, side, chainId, page, size } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getOrders(address, tokenId, side, chainId, page, size) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getOrders(address, tokenId, side, chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getOrders(address, tokenId, side, chainId) + default: + unreachable(provider) + } +} + +export async function getListings(options: ListOption) { + const { address, tokenId, chainId, provider } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getListings(address, tokenId, chainId) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getListings(address, tokenId, chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getListings(address, tokenId, chainId) + default: + unreachable(provider) + } +} + +export async function getHistory(options: HistoryOtpion) { + const { address, tokenId, chainId, provider, page, size } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getHistory(address, tokenId, chainId, page, size) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getHistory(address, tokenId, chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getHistory(address, tokenId) + default: + unreachable(provider) + } +} + +export async function getCollections(options: CollectionOption) { + const { address, chainId, provider, page, size } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getCollections(address, { chainId, page, size }) + case NonFungibleAssetProvider.RARIBLE: + return [] + case NonFungibleAssetProvider.NFTSCAN: + return [] + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts deleted file mode 100644 index 0b996d8f7ec9..000000000000 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFT.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' -import { unreachable } from '@dimensiondev/kit' -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' - -export async function getNFT(address: string, tokenId: string, chainId: ChainId, provider: NonFungibleAssetProvider) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getNFT(address, tokenId, chainId) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getNFT(address, tokenId, chainId) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getNFT(address, tokenId) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts deleted file mode 100644 index 23db0511f3af..000000000000 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTBalance.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { NFTScanApi } from '@masknet/web3-providers' -import { unreachable } from '@dimensiondev/kit' - -export async function getNFTBalance(address: string, chainId: ChainId, provider: NonFungibleAssetProvider) { - switch (provider) { - case NonFungibleAssetProvider.RARIBLE: - case NonFungibleAssetProvider.OPENSEA: - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getContractBalance(address) - - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts deleted file mode 100644 index 8fa51c16357b..000000000000 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTContract.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { unreachable } from '@dimensiondev/kit' -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' -import { OpenSeaApi } from '@masknet/web3-providers' - -export async function getNFTContract(contractAddress: string, chainId: ChainId, provider: NonFungibleAssetProvider) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - case NonFungibleAssetProvider.NFTSCAN: - return OpenSeaApi.getContract(contractAddress, chainId) - case NonFungibleAssetProvider.RARIBLE: - return - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts b/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts deleted file mode 100644 index 6b892e8bf8ab..000000000000 --- a/packages/mask/src/plugins/EVM/services/NFT/getNFTsPaged.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' - -import { unreachable } from '@dimensiondev/kit' -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' - -export async function getNFTsPaged( - from: string, - chainId: ChainId, - provider: NonFungibleAssetProvider, - page?: number, - size?: number, -) { - switch (provider) { - case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getNFTsPaged(from, { chainId, page, size }) - case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getNFTsPaged(from, { chainId, page, size }) - case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getNFTsPaged(from, { chainId, page, size }) - default: - unreachable(provider) - } -} diff --git a/packages/mask/src/plugins/EVM/services/NFT/index.ts b/packages/mask/src/plugins/EVM/services/NFT/index.ts index ac8b764d37f8..8db96d199828 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/index.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/index.ts @@ -1,4 +1,84 @@ -export * from './getNFTBalance' -export * from './getNFT' -export * from './getNFTContract' -export * from './getNFTsPaged' +import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { unreachable } from '@dimensiondev/kit' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' + +export interface NFTOption { + address: string + tokenId: string + chainId: ChainId + provider: NonFungibleAssetProvider +} + +export interface BalanceOption { + address: string + chainId: ChainId + provider: NonFungibleAssetProvider +} + +export interface ContractOption { + contractAddress: string + chainId: ChainId + provider: NonFungibleAssetProvider +} + +export interface NFTsByPaginationsOption { + from: string + chainId: ChainId + provider: NonFungibleAssetProvider + page: number + size: number +} + +export async function getNFT(options: NFTOption) { + const { address, tokenId, chainId, provider } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getNFT(address, tokenId, chainId) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getNFT(address, tokenId, chainId) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getNFT(address, tokenId) + default: + unreachable(provider) + } +} + +export async function getNFTBalance(options: BalanceOption) { + const { address, chainId, provider } = options + switch (provider) { + case NonFungibleAssetProvider.RARIBLE: + case NonFungibleAssetProvider.OPENSEA: + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getContractBalance(address) + + default: + unreachable(provider) + } +} + +export async function getNFTContract(options: ContractOption) { + const { contractAddress, chainId, provider } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + case NonFungibleAssetProvider.NFTSCAN: + return OpenSeaApi.getContract(contractAddress, chainId) + case NonFungibleAssetProvider.RARIBLE: + return + default: + unreachable(provider) + } +} + +export async function getNFTsByPaginations(options: NFTsByPaginationsOption) { + const { provider, from, chainId, page, size } = options + switch (provider) { + case NonFungibleAssetProvider.OPENSEA: + return OpenSeaApi.getNFTsByPaginations(from, { chainId, page, size }) + case NonFungibleAssetProvider.NFTSCAN: + return NFTScanApi.getNFTsByPaginations(from, { chainId, page, size }) + case NonFungibleAssetProvider.RARIBLE: + return RaribleApi.getNFTsByPaginations(from, { chainId, page, size }) + default: + unreachable(provider) + } +} diff --git a/packages/mask/src/plugins/Wallet/services/assets.ts b/packages/mask/src/plugins/Wallet/services/assets.ts index 2361e3e74daf..fd063da61027 100644 --- a/packages/mask/src/plugins/Wallet/services/assets.ts +++ b/packages/mask/src/plugins/Wallet/services/assets.ts @@ -43,7 +43,13 @@ export async function getCollectionsNFT( size?: number, ): Promise<{ collections: ERC721TokenCollectionInfo[]; hasNextPage: boolean }> { if (provider === NonFungibleAssetProvider.OPENSEA) { - const collections = await EVM_RPC.getCollections(address, chainId, provider, page, size) + const collections = await EVM_RPC.getCollections({ + address, + chainId, + provider, + page: page ?? 0, + size: size ?? 50, + }) return { collections, @@ -70,7 +76,13 @@ export async function getAssetsListNFT( assets: [], hasNextPage: false, } - const tokens = await EVM_RPC.getNFTsPaged(address, chainId, provider, page, size) + const tokens = await EVM_RPC.getNFTsByPaginations({ + from: address, + chainId, + provider, + page: page ?? 0, + size: size ?? 50, + }) return { assets: tokens, hasNextPage: tokens.length === size, diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 45768d0f2237..f01f03e682e8 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -109,7 +109,7 @@ export async function getNFT(address: string, tokenId: string, chainId: ChainId) return createERC721TokenAsset(data) } -export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { +export async function getNFTsByPaginations(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const { size = 50, page = 0 } = opts const response = await fetchAsset('getAllNftByUserAddress', { diff --git a/packages/web3-providers/src/opensea/constants.ts b/packages/web3-providers/src/opensea/constants.ts index 4322fac62d5c..336ce12bb910 100644 --- a/packages/web3-providers/src/opensea/constants.ts +++ b/packages/web3-providers/src/opensea/constants.ts @@ -1,3 +1 @@ -export const OpenSeaAccountURL = 'https://opensea.io/accounts/' -export const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' -export const OpenSea_API_URL = 'https://api.opensea.io' +export {} diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 68cd9adde724..855025532106 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -1,4 +1,3 @@ -import { OpenSeaAccountURL, OPENSEA_API_KEY, OpenSea_API_URL } from './constants' import type { OpenSeaAssetContract, OpenSeaAssetEvent, OpenSeaResponse, OpenSeaCollection } from './types' import urlcat from 'urlcat' import { head, uniqBy } from 'lodash-unified' @@ -15,28 +14,31 @@ import { ERC721TokenDetailed, EthereumTokenType, } from '@masknet/web3-shared-evm' + +const OpenSeaAccountURL = 'https://opensea.io/accounts/' +const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' +const OpenSea_API_URL = 'https://api.opensea.io' + async function fetchAsset(url: string, chainId: ChainId) { if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return - const response = await ( - await fetch(url, { - method: 'GET', - mode: 'cors', - headers: { - 'x-api-key': OPENSEA_API_KEY, - }, - }) - ).json() + const response = await fetch(url, { + method: 'GET', + mode: 'cors', + headers: { + 'x-api-key': OPENSEA_API_KEY, + }, + }) - return response as T + return response.json() as Promise } -export async function getNFTsPaged(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { +export async function getNFTsByPaginations(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>( - urlcat(`${OpenSea_API_URL}/api/v1/assets?owner=:from&limit=:limit&offset=:offset}`, { - from, + urlcat(OpenSea_API_URL, '/api/v1/assets', { + owner: from, offset: opts.page, limit: opts.size, }), @@ -81,14 +83,14 @@ function createERC721TokenAsset( async function _getAsset(address: string, tokenId: string, chainId: ChainId) { return fetchAsset( - urlcat(`${OpenSea_API_URL}/api/v1/asset/:address/:tokenId`, { address, tokenId }), + urlcat(OpenSea_API_URL, `/api/v1/asset/:address/:tokenId`, { address, tokenId }), chainId, ) } export async function getContract(address: string, chainId: ChainId) { const assetContract = await fetchAsset( - `${OpenSea_API_URL}/api/v1/asset_contract/${address}`, + urlcat(OpenSea_API_URL, '/api/v1/asset_contract/:address', { address }), chainId, ) diff --git a/packages/web3-providers/src/opensea/types.ts b/packages/web3-providers/src/opensea/types.ts index aa9895f5a13e..6bb5a311d4ea 100644 --- a/packages/web3-providers/src/opensea/types.ts +++ b/packages/web3-providers/src/opensea/types.ts @@ -35,12 +35,9 @@ interface NumericalTraitStats { min: number max: number } -interface StringTraitStats { - [key: string]: number -} interface OpenSeaTraitStats { - [traitName: string]: NumericalTraitStats | StringTraitStats + [traitName: string]: NumericalTraitStats | Record } interface OpenSeaFungibleToken { diff --git a/packages/web3-providers/src/rarible/helpers.ts b/packages/web3-providers/src/rarible/helpers.ts index 8298b5fcde2e..d78d6ffad7d8 100644 --- a/packages/web3-providers/src/rarible/helpers.ts +++ b/packages/web3-providers/src/rarible/helpers.ts @@ -2,6 +2,6 @@ import { RaribleIPFSURL } from './constants' export function toRaribleImage(url?: string) { if (!url) return '' - if (url.includes('ipfs://ipfs/')) return `${RaribleIPFSURL}${url.replace('ipfs://ipfs/', '')}` + if (url.startsWith('ipfs://ipfs/')) return url.replace(/^ipfs:\/\/ipfs\//, RaribleIPFSURL) return url } diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 2581a04cff3d..3e850a95e158 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -310,7 +310,7 @@ export async function getNFT(tokenAddress: string, tokenId: string) { return createERC721TokenAsset(tokenAddress, tokenId, asset) } -export async function getNFTsPaged(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { +export async function getNFTsByPaginations(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const asset = await fetchFromRarible<{ total: number continuation: string diff --git a/packages/web3-shared/evm/pipes/index.ts b/packages/web3-shared/evm/pipes/index.ts index 17e9f9e841af..d6b3c6628de3 100644 --- a/packages/web3-shared/evm/pipes/index.ts +++ b/packages/web3-shared/evm/pipes/index.ts @@ -146,7 +146,7 @@ export function resolveCollectibleProviderLink(chainId: ChainId, provider: NonFu case NonFungibleAssetProvider.RARIBLE: return 'https://rarible.com' case NonFungibleAssetProvider.NFTSCAN: - return 'https:/nftscan.com' + return 'https://nftscan.com' default: unreachable(provider) } From 858ab9ea9104233f1599b83cd844eb44c4c92f9a Mon Sep 17 00:00:00 2001 From: lelenei Date: Sat, 18 Dec 2021 22:01:26 +0800 Subject: [PATCH 10/24] fix: typo --- packages/mask/src/plugins/EVM/services/NFT/index.ts | 10 +++++----- packages/mask/src/plugins/Wallet/services/assets.ts | 2 +- packages/web3-providers/src/NFTScan/index.ts | 2 +- packages/web3-providers/src/opensea/index.ts | 2 +- packages/web3-providers/src/rarible/index.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/mask/src/plugins/EVM/services/NFT/index.ts b/packages/mask/src/plugins/EVM/services/NFT/index.ts index 8db96d199828..dfed14a17048 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/index.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/index.ts @@ -21,7 +21,7 @@ export interface ContractOption { provider: NonFungibleAssetProvider } -export interface NFTsByPaginationsOption { +export interface NFTsByPaginationOption { from: string chainId: ChainId provider: NonFungibleAssetProvider @@ -69,15 +69,15 @@ export async function getNFTContract(options: ContractOption) { } } -export async function getNFTsByPaginations(options: NFTsByPaginationsOption) { +export async function getNFTsByPagination(options: NFTsByPaginationOption) { const { provider, from, chainId, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getNFTsByPaginations(from, { chainId, page, size }) + return OpenSeaApi.getNFTsByPagination(from, { chainId, page, size }) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getNFTsByPaginations(from, { chainId, page, size }) + return NFTScanApi.getNFTsByPagination(from, { chainId, page, size }) case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getNFTsByPaginations(from, { chainId, page, size }) + return RaribleApi.getNFTsByPagination(from, { chainId, page, size }) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/Wallet/services/assets.ts b/packages/mask/src/plugins/Wallet/services/assets.ts index fd063da61027..9355ecc69789 100644 --- a/packages/mask/src/plugins/Wallet/services/assets.ts +++ b/packages/mask/src/plugins/Wallet/services/assets.ts @@ -76,7 +76,7 @@ export async function getAssetsListNFT( assets: [], hasNextPage: false, } - const tokens = await EVM_RPC.getNFTsByPaginations({ + const tokens = await EVM_RPC.getNFTsByPagination({ from: address, chainId, provider, diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index f01f03e682e8..083f58424a86 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -109,7 +109,7 @@ export async function getNFT(address: string, tokenId: string, chainId: ChainId) return createERC721TokenAsset(data) } -export async function getNFTsByPaginations(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { +export async function getNFTsByPagination(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const { size = 50, page = 0 } = opts const response = await fetchAsset('getAllNftByUserAddress', { diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 855025532106..29d5d89be0bc 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -33,7 +33,7 @@ async function fetchAsset(url: string, chainId: ChainId) { return response.json() as Promise } -export async function getNFTsByPaginations(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { +export async function getNFTsByPagination(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>( diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 3e850a95e158..c0d867fd6135 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -310,7 +310,7 @@ export async function getNFT(tokenAddress: string, tokenId: string) { return createERC721TokenAsset(tokenAddress, tokenId, asset) } -export async function getNFTsByPaginations(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { +export async function getNFTsByPagination(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const asset = await fetchFromRarible<{ total: number continuation: string From fd3ea79aeaf65800bdf0e5892190bd253f900ddc Mon Sep 17 00:00:00 2001 From: lelenei Date: Mon, 20 Dec 2021 12:35:10 +0800 Subject: [PATCH 11/24] fix: typo --- packages/mask/src/plugins/EVM/services/Asset/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mask/src/plugins/EVM/services/Asset/index.ts b/packages/mask/src/plugins/EVM/services/Asset/index.ts index 10ba11903524..e9dea1ccd985 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/index.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/index.ts @@ -28,7 +28,7 @@ export interface ListOption { provider: NonFungibleAssetProvider } -export interface HistoryOtpion { +export interface HistoryOption { address: string tokenId: string chainId: ChainId @@ -86,7 +86,7 @@ export async function getListings(options: ListOption) { } } -export async function getHistory(options: HistoryOtpion) { +export async function getHistory(options: HistoryOption) { const { address, tokenId, chainId, provider, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: From 786037eee71ea52db3faeca6b032c9549e0fa59d Mon Sep 17 00:00:00 2001 From: Septs Date: Mon, 20 Dec 2021 16:44:23 +0800 Subject: [PATCH 12/24] chore: improve code style --- .../SNSAdaptor/ListingByHighestBidCard.tsx | 9 +- .../SNSAdaptor/ListingByPriceCard.tsx | 9 +- .../SNSAdaptor/MakeOfferDialog.tsx | 9 +- .../Collectible/SNSAdaptor/OrderRow.tsx | 3 +- .../Collectible/apis/openseaFromSDK.ts | 13 +- .../mask/src/plugins/Collectible/utils.ts | 12 ++ .../web3-providers/src/NFTScan/constants.ts | 4 +- packages/web3-providers/src/NFTScan/index.ts | 28 ++-- packages/web3-providers/src/opensea/index.ts | 158 ++++++++---------- packages/web3-providers/src/rarible/index.ts | 114 ++++++------- pnpm-lock.yaml | 22 ++- 11 files changed, 175 insertions(+), 206 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx index 5d13e36df26f..c8deb5d0cbb1 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByHighestBidCard.tsx @@ -19,7 +19,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' -import { WyvernSchemaName } from 'opensea-js/lib/types' +import { isWyvernSchemaName } from '../utils' const useStyles = makeStyles()((theme) => ({ footer: { @@ -71,15 +71,12 @@ export function ListingByHighestBidCard(props: ListingByHighestBidCardProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.ERC20) return + const schemaName = asset.value.asset_contract?.schemaName await PluginCollectibleRPC.createSellOrder({ asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: Object.values(WyvernSchemaName).includes( - asset.value.asset_contract?.schemaName as WyvernSchemaName, - ) - ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) - : undefined, + schemaName: isWyvernSchemaName(schemaName) ? schemaName : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx index c79200cb296e..f73beff5e418 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ListingByPriceCard.tsx @@ -21,7 +21,7 @@ import { PluginCollectibleRPC } from '../messages' import { toAsset } from '../helpers' import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' -import { WyvernSchemaName } from 'opensea-js/lib/types' +import { isWyvernSchemaName } from '../utils' const useStyles = makeStyles()((theme) => { return { @@ -101,15 +101,12 @@ export function ListingByPriceCard(props: ListingByPriceCardProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.Native && token.value.type !== EthereumTokenType.ERC20) return + const schemaName = asset.value.asset_contract?.schemaName await PluginCollectibleRPC.createSellOrder({ asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: Object.values(WyvernSchemaName).includes( - asset.value.asset_contract?.schemaName as WyvernSchemaName, - ) - ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) - : undefined, + schemaName: isWyvernSchemaName(schemaName) ? schemaName : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 3f555a79fd8d..d25239d5cf5d 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -37,7 +37,7 @@ import getUnixTime from 'date-fns/getUnixTime' import type { useAsset } from '../../EVM/hooks' import { rightShift, ZERO } from '@masknet/web3-shared-base/utils/number' import type { Coin } from '../../Trader/types' -import { WyvernSchemaName } from 'opensea-js/lib/types' +import { isWyvernSchemaName } from '../utils' const useStyles = makeStyles()((theme) => { return { @@ -98,15 +98,12 @@ export function MakeOfferDialog(props: MakeOfferDialogProps) { if (!asset.value.token_id || !asset.value.token_address) return if (!token?.value) return if (token.value.type !== EthereumTokenType.Native && token.value.type !== EthereumTokenType.ERC20) return + const schemaName = asset.value.asset_contract?.schemaName await PluginCollectibleRPC.createBuyOrder({ asset: toAsset({ tokenId: asset.value.token_id, tokenAddress: asset.value.token_address, - schemaName: Object.values(WyvernSchemaName).includes( - asset.value.asset_contract?.schemaName as WyvernSchemaName, - ) - ? (asset.value.asset_contract?.schemaName as WyvernSchemaName) - : undefined, + schemaName: isWyvernSchemaName(schemaName) ? schemaName : undefined, }), accountAddress: account, startAmount: Number.parseFloat(amount), diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index 22e554e3d64a..af5398023f74 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -16,6 +16,7 @@ import { FormattedBalance } from '@masknet/shared' import { getOrderUnitPrice } from '@masknet/web3-providers' import type { AssetOrder } from '@masknet/web3-providers' import formatDistanceToNow from 'date-fns/formatDistanceToNow' +import urlcat from 'urlcat' const useStyles = makeStyles()((theme) => { return { @@ -68,7 +69,7 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { const link = useMemo(() => { return provider === NonFungibleAssetProvider.OPENSEA - ? `https://opensea.io/accounts/${address}` + ? urlcat('https://opensea.io/accounts/:address', { address }) : order.maker_account?.link }, [order, provider]) diff --git a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts index 771853f380f1..003b913b143d 100644 --- a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts +++ b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts @@ -18,14 +18,11 @@ function createExternalProvider() { } async function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby) { - return new OpenSeaPort( - createExternalProvider(), - { - networkName: resolveOpenSeaNetwork(chainId), - apiKey: OpenSeaAPI_Key, - }, - console.log, - ) + const config = { + networkName: resolveOpenSeaNetwork(chainId), + apiKey: OpenSeaAPI_Key, + } + return new OpenSeaPort(createExternalProvider(), config, console.log) } async function createOpenSeaPort(chainId?: ChainId) { diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index c890094b06ef..cdc32889fc9b 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -9,6 +9,7 @@ import { import { ChainId, NonFungibleAssetProvider, formatBalance } from '@masknet/web3-shared-evm' import type { AssetEvent } from './types' import { multipliedBy } from '@masknet/web3-shared-base' +import { WyvernSchemaName } from 'opensea-js/lib/types' export function checkUrl(url: string): boolean { const protocol = 'https://' @@ -81,3 +82,14 @@ export function getLastSalePrice(lastSale: AssetEvent | null) { const price = formatBalance(lastSale.total_price, lastSale.payment_token.decimals) return price } + +export function isWyvernSchemaName(name: unknown): name is WyvernSchemaName { + const schemas: unknown[] = [ + WyvernSchemaName.ERC20, + WyvernSchemaName.ERC721, + WyvernSchemaName.ERC1155, + WyvernSchemaName.LegacyEnjin, + WyvernSchemaName.ENSShortNameAuction, + ] + return schemas.includes(name) +} diff --git a/packages/web3-providers/src/NFTScan/constants.ts b/packages/web3-providers/src/NFTScan/constants.ts index e0297e9e78de..63909d58d99c 100644 --- a/packages/web3-providers/src/NFTScan/constants.ts +++ b/packages/web3-providers/src/NFTScan/constants.ts @@ -1,4 +1,6 @@ +import urlcat from 'urlcat' + export const NFTSCAN_ID = 't9k2o5GC' export const NFTSCAN_SECRET = '21da1d638ef5d0bf76e37aa5c2da7fd789ade9e3' export const NFTSCAN_URL = 'https://restapi.nftscan.com' -export const NFTSCAN_BASE_API = `${NFTSCAN_URL}/api/v1` +export const NFTSCAN_BASE_API = urlcat(NFTSCAN_URL, '/api/v1') diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 083f58424a86..938e8b6e661a 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -10,18 +10,15 @@ const tokenCache = new Map<'token', { token: string; expiration: number }>() async function getToken() { const token = tokenCache.get('token') if (token && Date.now() <= token.expiration) return token.token - - const response = await fetch( - urlcat(NFTSCAN_URL, '/gw/token', { - apiKey: NFTSCAN_ID, - apiSecret: NFTSCAN_SECRET, - }), - { - mode: 'cors', - }, - ) - - const { data }: { data: { accessToken: string; expiration: number } } = await response.json() + const requestPath = urlcat(NFTSCAN_URL, '/gw/token', { + apiKey: NFTSCAN_ID, + apiSecret: NFTSCAN_SECRET, + }) + const response = await fetch(requestPath, { mode: 'cors' }) + interface Payload { + data: { accessToken: string; expiration: number } + } + const { data }: Payload = await response.json() tokenCache.set('token', { token: data.accessToken, expiration: addSeconds(Date.now(), data.expiration).getTime(), @@ -44,12 +41,7 @@ async function fetchAsset(path: string, config: Partial = {}) { } function createERC721ContractDetailedFromAssetContract(asset: NFTScanAsset) { - return createERC721ContractDetailed( - ChainId.Mainnet, - asset.trade_contract, - 'unknown name', - asset.trade_symbol ?? 'unknown symbol', - ) + return createERC721ContractDetailed(ChainId.Mainnet, asset.trade_contract, undefined, asset.trade_symbol) } function createERC721TokenAsset(asset: NFTScanAsset) { diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 29d5d89be0bc..d714fdf55674 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -15,35 +15,28 @@ import { EthereumTokenType, } from '@masknet/web3-shared-evm' -const OpenSeaAccountURL = 'https://opensea.io/accounts/' +const OPENSEA_ACCOUNT_URL = 'https://opensea.io/accounts/' const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' -const OpenSea_API_URL = 'https://api.opensea.io' +const OPENSEA_API_URL = 'https://api.opensea.io' async function fetchAsset(url: string, chainId: ChainId) { if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return - - const response = await fetch(url, { + const response = await fetch(urlcat(OPENSEA_API_URL, url), { method: 'GET', mode: 'cors', - headers: { - 'x-api-key': OPENSEA_API_KEY, - }, + headers: { 'x-api-key': OPENSEA_API_KEY }, }) - return response.json() as Promise } export async function getNFTsByPagination(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts - - const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>( - urlcat(OpenSea_API_URL, '/api/v1/assets', { - owner: from, - offset: opts.page, - limit: opts.size, - }), - chainId, - ) + const requestPath = urlcat('/api/v1/assets', { + owner: from, + offset: page, + limit: size, + }) + const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>(requestPath, chainId) if (!asset) return [] return asset.assets @@ -82,18 +75,13 @@ function createERC721TokenAsset( } async function _getAsset(address: string, tokenId: string, chainId: ChainId) { - return fetchAsset( - urlcat(OpenSea_API_URL, `/api/v1/asset/:address/:tokenId`, { address, tokenId }), - chainId, - ) + const requestPath = urlcat(`/api/v1/asset/:address/:tokenId`, { address, tokenId }) + return fetchAsset(requestPath, chainId) } export async function getContract(address: string, chainId: ChainId) { - const assetContract = await fetchAsset( - urlcat(OpenSea_API_URL, '/api/v1/asset_contract/:address', { address }), - chainId, - ) - + const requestPath = urlcat('/api/v1/asset_contract/:address', { address }) + const assetContract = await fetchAsset(requestPath, chainId) return createERC721ContractDetailedAsset(address, chainId, assetContract) } @@ -133,11 +121,11 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { current_symbol: desktopOrder?.payment_token_contract?.symbol ?? 'ETH', owner: { ...asset.owner, - link: `${OpenSeaAccountURL}${asset.owner?.user?.username ?? asset.owner.address ?? ''}`, + link: `${OPENSEA_ACCOUNT_URL}${asset.owner?.user?.username ?? asset.owner.address ?? ''}`, }, creator: { ...asset.creator, - link: `${OpenSeaAccountURL}${asset.creator?.user?.username ?? asset.creator?.address ?? ''}`, + link: `${OPENSEA_ACCOUNT_URL}${asset.creator?.user?.username ?? asset.creator?.address ?? ''}`, }, token_id: asset.token_id, token_address: asset.token_address, @@ -161,15 +149,13 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { ).filter((x) => x.type === EthereumTokenType.ERC20), slug: asset.collection.slug, desktopOrder, - top_ownerships: asset.top_ownerships.map((x) => ({ + top_ownerships: asset.top_ownerships.map((x): { owner: NFTAssetOwner } => ({ owner: { address: x.owner.address, profile_img_url: x.owner.profile_img_url, - user: { - username: x.owner.user?.username ?? '', - }, + user: { username: x.owner.user?.username ?? '' }, link: '', - } as NFTAssetOwner, + }, })), collection: asset.collection as unknown as AssetCollection, response_: asset as any, @@ -184,29 +170,26 @@ export async function getAsset(address: string, tokenId: string, chainId: ChainI } export async function getHistory(address: string, tokenId: string, chainId: ChainId, page: number, size: number) { - const fetchResponse = await ( - await fetch( - urlcat(OpenSea_API_URL, '/api/v1/events', { - asset_contract_address: address, - token_id: tokenId, - offset: page, - limit: size, - }), - { - mode: 'cors', - headers: { - 'x-api-key': OPENSEA_API_KEY, - }, - }, - ) - ).json() - - const { asset_events }: { asset_events: OpenSeaAssetEvent[] } = fetchResponse - + const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/events', { + asset_contract_address: address, + token_id: tokenId, + offset: page, + limit: size, + }) + const response = await fetch(requestPath, { + mode: 'cors', + headers: { 'x-api-key': OPENSEA_API_KEY }, + }) + interface Payload { + asset_events: OpenSeaAssetEvent[] + } + const { asset_events }: Payload = await response.json() return asset_events.map(createNFTHistory) } function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { + const makeLink = (account?: OpenSeaAssetEvent['seller']) => + urlcat(OPENSEA_ACCOUNT_URL, '/:address', { address: account?.user?.username ?? account?.address }) const accountPair = event.event_type === 'successful' ? { @@ -214,15 +197,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.seller?.user?.username, address: event.seller?.address, imageUrl: event.seller?.profile_img_url, - link: `${OpenSeaAccountURL}${event.seller?.user?.username ?? event.seller?.address}`, + link: makeLink(event.seller), }, to: { username: event.winner_account?.user?.username, address: event.winner_account?.address, imageUrl: event.winner_account?.profile_img_url, - link: `${OpenSeaAccountURL}${ - event.winner_account?.user?.username ?? event.winner_account?.address - }`, + link: makeLink(event.winner_account), }, } : { @@ -230,13 +211,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.from_account?.user?.username, address: event.from_account?.address, imageUrl: event.from_account?.profile_img_url, - link: `${OpenSeaAccountURL}${event.from_account?.user?.username ?? event.from_account?.address}`, + link: makeLink(event.from_account), }, to: { username: event.to_account?.user?.username, address: event.to_account?.address, imageUrl: event.to_account?.profile_img_url, - link: `${OpenSeaAccountURL}${event.to_account?.user?.username ?? event.to_account?.address}`, + link: makeLink(event.to_account), }, } return { @@ -266,27 +247,24 @@ async function fetchOrders( page: number, size: number, ) { - const response = await fetch( - urlcat(OpenSea_API_URL, '/wyvern/v1/orders', { - asset_contract_address: address, - token_id: tokenId, - side, - offset: page, - limit: size, - }), - { - method: 'GET', - mode: 'cors', - headers: { - Accept: 'application/json', - 'x-api-key': OPENSEA_API_KEY, - }, - }, - ) - + const requestPath = urlcat(OPENSEA_API_URL, '/wyvern/v1/orders', { + asset_contract_address: address, + token_id: tokenId, + side, + offset: page, + limit: size, + }) + const response = await fetch(requestPath, { + method: 'GET', + mode: 'cors', + headers: { Accept: 'application/json', 'x-api-key': OPENSEA_API_KEY }, + }) if (!response.ok) return [] - const { orders = [] }: { orders: AssetOrder[] } = await response.json() - return orders + interface Payload { + orders: AssetOrder[] + } + const payload: Payload = await response.json() + return payload.orders ?? [] } export async function getOrders( @@ -301,20 +279,16 @@ export async function getOrders( } export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const { collections }: { collections: OpenSeaCollection[] } = await ( - await fetch( - urlcat(OpenSea_API_URL, '/api/v1/collections', { - offset: opts.page, - limit: opts.size, - }), - { - headers: { - 'x-api-key': OPENSEA_API_KEY, - }, - }, - ) - ).json() - + const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/collections', { + address, + offset: opts.page, + limit: opts.size, + }) + const response = await fetch(requestPath, { headers: { 'x-api-key': OPENSEA_API_KEY } }) + interface Payload { + collections: OpenSeaCollection[] + } + const { collections }: Payload = await response.json() return collections.map((x) => ({ name: x.name, image: x.image_url || undefined, diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index c0d867fd6135..deefd2bdb529 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -14,15 +14,9 @@ import { EthereumTokenType, FungibleTokenDetailed, } from '@masknet/web3-shared-evm' -import { - RaribleUserURL, - RaribleRopstenUserURL, - RaribleMainnetURL, - RaribleMainnetAPI_URL, - RaribleChainURL, -} from './constants' +import { RaribleUserURL, RaribleRopstenUserURL, RaribleMainnetURL } from './constants' import urlcat from 'urlcat' -import { AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' +import { AssetOrder, NFTAsset, NFTHistory, OrderSide } from '../types' import { compact, first } from 'lodash-unified' import { toRaribleImage } from './helpers' @@ -34,19 +28,17 @@ export const resolveRaribleUserNetwork = createLookupTableResolver(root: string, subPath: string, config = {} as RequestInit) { - const response = await ( - await fetch(urlcat(root, subPath), { - mode: 'cors', - ...config, - }) - ).json() +export async function fetchFromRarible(path: string, init?: RequestInit) { + const response = await fetch(urlcat(RaribleMainnetURL, path), { + mode: 'cors', + ...init, + }) - return response as T + return response.json() as Promise } export async function getProfilesFromRarible(addresses: (string | undefined)[]) { - return fetchFromRarible(RaribleMainnetURL, 'profiles/list', { + return fetchFromRarible('/profiles/list', { method: 'POST', body: JSON.stringify(addresses), headers: { @@ -56,25 +48,19 @@ export async function getProfilesFromRarible(addresses: (string | undefined)[]) } export async function getOffers(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { - const orders = await fetchFromRarible( - RaribleMainnetURL, - `items/${tokenAddress}:${tokenId}/offers`, - { - method: 'POST', - body: JSON.stringify({ size: 20 }), - headers: { - 'content-type': 'application/json', - }, - }, - ) + const requestPath = urlcat('/items/:tokenAddress::tokenId/offers', { tokenAddress, tokenId }) + const orders = await fetchFromRarible(requestPath, { + method: 'POST', + body: JSON.stringify({ size: 20 }), + headers: { 'content-type': 'application/json' }, + }) const profiles = await getProfilesFromRarible(orders.map((item) => item.maker)) - return orders.map((order) => { + return orders.map((order): AssetOrder => { const ownerInfo = profiles.find((owner) => owner.id === order.maker) return { created_time: order.updateDate, current_price: order.buyPriceEth, current_bounty: order.fee, - payment_token: order.token, listing_time: 0, side: OrderSide.Buy, @@ -83,22 +69,21 @@ export async function getOffers(tokenAddress: string, tokenId: string, chainId: order_hash: order.signature, approved_on_chain: false, maker_account: { - user: { - username: ownerInfo?.name ?? '', - }, + user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', profile_img_url: toRaribleImage(ownerInfo?.image), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, - } as NFTAssetOwner, - } as AssetOrder + }, + } }) } export async function getListings(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { - const assets = await fetchFromRarible(RaribleMainnetURL, `items/${tokenAddress}:${tokenId}/ownerships`) + const requestPath = urlcat('/items/:tokenAddress::tokenId/ownerships', { tokenAddress, tokenId }) + const assets = await fetchFromRarible(requestPath) const listings = assets.filter((x) => x.selling) const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) - return listings.map((asset) => { + return listings.map((asset): AssetOrder => { const ownerInfo = profiles.find((owner) => owner.id === asset.owner) return { created_time: asset.date, @@ -111,13 +96,11 @@ export async function getListings(tokenAddress: string, tokenId: string, chainId expiration_time: 0, order_hash: asset.signature, maker_account: { - user: { - username: ownerInfo?.name ?? '', - }, + user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', profile_img_url: toRaribleImage(ownerInfo?.image), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, - } as NFTAssetOwner, + }, } }) } @@ -134,7 +117,7 @@ export async function getOrders(tokenAddress: string, tokenId: string, side: Ord } export async function getHistory(tokenAddress: string, tokenId: string): Promise { - let histories = await fetchFromRarible(RaribleMainnetURL, `activity`, { + let histories = await fetchFromRarible(`/activity`, { method: 'POST', body: JSON.stringify({ // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], @@ -280,21 +263,16 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): NFT } async function _getAsset(address: string, tokenId: string) { - const assetResponse = await fetchFromRarible( - RaribleChainURL, - urlcat('/v0.1/nft/items/:address::tokenId', { - includeMeta: true, - address, - tokenId, - }), - { - method: 'GET', - mode: 'cors', - headers: { - 'content-type': 'application/json', - }, - }, - ) + const requestPath = urlcat('/v0.1/nft/items/:address::tokenId', { + includeMeta: true, + address, + tokenId, + }) + const assetResponse = await fetchFromRarible(requestPath, { + method: 'GET', + mode: 'cors', + headers: { 'content-type': 'application/json' }, + }) return assetResponse } @@ -310,25 +288,27 @@ export async function getNFT(tokenAddress: string, tokenId: string) { return createERC721TokenAsset(tokenAddress, tokenId, asset) } -export async function getNFTsByPagination(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const asset = await fetchFromRarible<{ +export async function getNFTsByPagination( + from: string, + opts: { chainId: ChainId; page?: number; size?: number }, +): Promise { + const requestPath = urlcat('/ethereum/nft/items/byOwner', { owner: from, size: opts.size }) + interface Payload { total: number continuation: string items: RaribleNFTItemMapResponse[] - }>(RaribleMainnetAPI_URL, urlcat('/ethereum/nft/items/byOwner', { owner: from, size: opts.size }), {}) - if (!asset) return [] as ERC721TokenDetailed[] - + } + const asset = await fetchFromRarible(requestPath) + if (!asset) return [] return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) } export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const response = await fetchFromRarible(RaribleMainnetURL, `collections/${address}}`, { + const requestPath = urlcat('/collections/:address', { address }) + const response = await fetchFromRarible(requestPath, { method: 'POST', body: JSON.stringify({ size: 20 }), - headers: { - 'content-type': 'application/json', - }, + headers: { 'content-type': 'application/json' }, }) - return [response] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f36133ff573..91fd33ccdeec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24779,6 +24779,26 @@ packages: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} dev: true + github.com/ProjectOpenSea/wyvern-js/1634ad0e2f9961accca62edbf4ac624effa18552: + resolution: {tarball: https://codeload.github.com/ProjectOpenSea/wyvern-js/tar.gz/1634ad0e2f9961accca62edbf4ac624effa18552} + name: wyvern-js + version: 3.2.0 + dependencies: + '@0xproject/abi-gen': 0.1.7 + '@0xproject/assert': 0.0.11 + '@0xproject/json-schemas': 0.7.24 + '@0xproject/utils': 0.1.3 + '@0xproject/web3-wrapper': 0.1.14 + 0x.js: 0.29.2 + bn.js: 4.12.0 + ethereumjs-abi: github.com/ProjectWyvern/ethereumjs-abi/3d2d89641a6ad5984929b6ca4b646452ec74f73d + ethereumjs-util: 5.2.1 + json-loader: 0.5.7 + jsonschema: 1.4.0 + lodash: 4.17.21 + web3: 0.20.7 + dev: false + github.com/ProjectOpenSea/wyvern-js/fabb7660f23f2252c141077e32193d281036299e: resolution: {tarball: https://codeload.github.com/ProjectOpenSea/wyvern-js/tar.gz/fabb7660f23f2252c141077e32193d281036299e} name: wyvern-js @@ -24821,7 +24841,7 @@ packages: bignumber.js: 6.0.0 typed-promisify: 0.4.0 web3-provider-engine: 13.8.0 - wyvern-js: github.com/ProjectOpenSea/wyvern-js/fabb7660f23f2252c141077e32193d281036299e + wyvern-js: github.com/ProjectOpenSea/wyvern-js/1634ad0e2f9961accca62edbf4ac624effa18552 transitivePeerDependencies: - debug dev: false From 1fe9566dd5cc7627ec48434a2530dbba0eb08089 Mon Sep 17 00:00:00 2001 From: Septs Date: Mon, 20 Dec 2021 17:07:49 +0800 Subject: [PATCH 13/24] chore: improve code style --- .../mask/src/plugins/Avatar/utils/index.ts | 17 +++++----- .../Collectible/SNSAdaptor/ActionBar.tsx | 15 +++++---- .../Collectible/hooks/useAssetOrder.ts | 31 ++++++------------- .../mask/src/plugins/Collectible/utils.ts | 5 ++- packages/web3-providers/src/utils/index.ts | 24 +++++++------- 5 files changed, 40 insertions(+), 52 deletions(-) diff --git a/packages/mask/src/plugins/Avatar/utils/index.ts b/packages/mask/src/plugins/Avatar/utils/index.ts index cd44dd2b865f..562e0c5fae7c 100644 --- a/packages/mask/src/plugins/Avatar/utils/index.ts +++ b/packages/mask/src/plugins/Avatar/utils/index.ts @@ -1,9 +1,9 @@ -import BigNumber from 'bignumber.js' import { isNull } from 'lodash-unified' import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../../EVM/messages' import Services from '../../../extension/service' import { getOrderUnitPrice } from '@masknet/web3-providers' +import { ONE } from '@masknet/web3-shared-base' export async function getNFT(address: string, tokenId: string) { const asset = await EVM_RPC.getAsset({ @@ -12,15 +12,14 @@ export async function getNFT(address: string, tokenId: string) { chainId: ChainId.Mainnet, provider: NonFungibleAssetProvider.OPENSEA, }) - + const amount = + getOrderUnitPrice( + asset?.desktopOrder?.current_price, + asset?.desktopOrder?.payment_token_contract?.decimals ?? 0, + asset?.desktopOrder?.quantity ?? '1', + ) ?? ONE return { - amount: new BigNumber( - getOrderUnitPrice( - asset?.desktopOrder?.current_price, - asset?.desktopOrder?.payment_token_contract?.decimals ?? 0, - asset?.desktopOrder?.quantity ?? '1', - ) ?? 0, - ).toFixed(), + amount: amount.toFixed(), name: asset?.name ?? '', symbol: asset?.desktopOrder?.payment_token_contract?.symbol ?? 'ETH', image: asset?.image_url ?? '', diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx index 64282828b8e7..c365e1eea300 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/ActionBar.tsx @@ -7,7 +7,6 @@ import { useControlledDialog } from '../../../utils/hooks/useControlledDialog' import { MakeOfferDialog } from './MakeOfferDialog' import { PostListingDialog } from './PostListingDialog' import { CheckoutDialog } from './CheckoutDialog' -import { useAccount } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => { return { @@ -27,8 +26,8 @@ export interface ActionBarProps {} export function ActionBar(props: ActionBarProps) { const { t } = useI18N() const { classes } = useStyles() - const account = useAccount() - const { asset, assetOrder, token } = CollectibleState.useContainer() + const { asset, assetOrder } = CollectibleState.useContainer() + const assets = asset.value const { open: openCheckoutDialog, @@ -42,11 +41,11 @@ export function ActionBar(props: ActionBarProps) { onClose: onCloseListingDialog, } = useControlledDialog() - if (!asset.value) return null + if (!assets) return null return ( - {!asset.value.isOwner && asset.value.is_auction ? ( + {!assets.isOwner && assets.is_auction ? ( ) : null} - {!asset.value.isOwner && !asset.value.is_auction && asset.value?.desktopOrder ? ( + {!assets.isOwner && !assets.is_auction && assets?.desktopOrder ? ( ) : null} - {!asset.value.isOwner && !asset.value.is_auction ? ( + {!assets.isOwner && !assets.is_auction ? ( ) : null} - {asset.value.isOwner ? ( + {assets.isOwner ? ( - new BigNumber( - getOrderUnitPrice( - a.currentPrice?.toFixed(), - a.paymentTokenContract?.decimals ?? 0, - a.quantity.toFixed(), - ) ?? 0, - ).toNumber() - - new BigNumber( - getOrderUnitPrice( - b.currentPrice?.toFixed(), - b.paymentTokenContract?.decimals, - b.quantity.toFixed(), - ) ?? 0, - ).toNumber(), - ), - ) + const getPrice = (order: Order) => + getOrderUnitPrice( + order.currentPrice?.toFixed(), + order.paymentTokenContract?.decimals ?? 0, + order.quantity.toFixed(), + ) ?? ONE + const sellOrders = openSeaResponse.sellOrders ?? [] + const desktopOrder = head(sellOrders.sort((a, b) => getPrice(a).toNumber() - getPrice(b).toNumber())) return desktopOrder case NonFungibleAssetProvider.RARIBLE: diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index cdc32889fc9b..2e5d5393b607 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -66,15 +66,14 @@ export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quan if (!currentPrice || !decimals || !quantity) return const price = formatBalance(currentPrice, decimals) const _quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) - return new BigNumber(price).dividedBy(_quantity).toFixed(4, 1).toString() + return new BigNumber(price).dividedBy(_quantity).decimalPlaces(4, 1) } export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { if (!currentPrice || !decimals) return const price = formatBalance(usdPrice, 0) const quantity = formatBalance(currentPrice, decimals) - - return multipliedBy(price, quantity).toFixed(2, 1).toString() + return multipliedBy(price, quantity).decimalPlaces(2, 1) } export function getLastSalePrice(lastSale: AssetEvent | null) { diff --git a/packages/web3-providers/src/utils/index.ts b/packages/web3-providers/src/utils/index.ts index 1587c5279ce7..54ec708cab57 100644 --- a/packages/web3-providers/src/utils/index.ts +++ b/packages/web3-providers/src/utils/index.ts @@ -3,29 +3,31 @@ import BigNumber from 'bignumber.js' export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { if (!currentPrice || !decimals || !quantity) return - const price = formatBalance(currentPrice, decimals) - const _quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) - return new BigNumber(price).dividedBy(_quantity).toFixed(4, 1) + currentPrice = formatBalance(currentPrice, decimals) + quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) + return new BigNumber(currentPrice).dividedBy(quantity).decimalPlaces(4, 1) } export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { if (!currentPrice || !decimals) return const price = formatBalance(usdPrice, 0) const quantity = formatBalance(currentPrice, decimals) - - return new BigNumber(price).multipliedBy(quantity).toFixed(2, 1) + return new BigNumber(price).multipliedBy(quantity).decimalPlaces(2, 1) } export function getLastSalePrice(total_price?: string, decimals?: number) { if (!total_price || !decimals) return - const price = formatBalance(total_price, decimals) - return price + return formatBalance(total_price, decimals) +} + +interface Token { + address: string + decimals: number + name: string + symbol: string } -export function toTokenDetailed( - chainId: ChainId, - token: { address: string; decimals: number; name: string; symbol: string }, -) { +export function toTokenDetailed(chainId: ChainId, token: Token) { if (token.symbol === 'ETH') return createNativeToken(chainId) return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) } From f56803db88478f18ec958e604c780b77f5e13e59 Mon Sep 17 00:00:00 2001 From: Septs Date: Mon, 20 Dec 2021 17:12:47 +0800 Subject: [PATCH 14/24] chore: improve code style --- .../src/plugins/Collectible/apis/openseaFromSDK.ts | 14 +++++++------- .../shared/src/UI/components/TokenIcon/index.tsx | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts index 003b913b143d..5b7108da4ed2 100644 --- a/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts +++ b/packages/mask/src/plugins/Collectible/apis/openseaFromSDK.ts @@ -17,7 +17,7 @@ function createExternalProvider() { } } -async function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby) { +function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby) { const config = { networkName: resolveOpenSeaNetwork(chainId), apiKey: OpenSeaAPI_Key, @@ -25,28 +25,28 @@ async function createOpenSeaPortChain(chainId: ChainId.Mainnet | ChainId.Rinkeby return new OpenSeaPort(createExternalProvider(), config, console.log) } -async function createOpenSeaPort(chainId?: ChainId) { +function createOpenSeaPort(chainId?: ChainId) { return createOpenSeaPortChain(chainId ?? currentChainIdSettings.value) } export async function getAssetFromSDK(tokenAddress: string, tokenId: string, chainId?: ChainId) { - const _chainId = chainId ?? currentChainIdSettings.value - return (await createOpenSeaPort(_chainId)).api.getAsset({ tokenAddress, tokenId }) + chainId = chainId ?? currentChainIdSettings.value + return createOpenSeaPort(chainId).api.getAsset({ tokenAddress, tokenId }) } export async function createBuyOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).createBuyOrder({ + return createOpenSeaPort().createBuyOrder({ referrerAddress: ReferrerAddress, ...payload, }) } export async function createSellOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).createSellOrder(payload) + return createOpenSeaPort().createSellOrder(payload) } export async function fulfillOrder(payload: Parameters[0]) { - return (await createOpenSeaPort()).fulfillOrder({ + return createOpenSeaPort().fulfillOrder({ referrerAddress: ReferrerAddress, ...payload, }) diff --git a/packages/shared/src/UI/components/TokenIcon/index.tsx b/packages/shared/src/UI/components/TokenIcon/index.tsx index 8f2b57ee3661..c4d46019f87f 100644 --- a/packages/shared/src/UI/components/TokenIcon/index.tsx +++ b/packages/shared/src/UI/components/TokenIcon/index.tsx @@ -44,16 +44,16 @@ export interface TokenIconProps extends withClasses<'icon'> { } export function TokenIcon(props: TokenIconProps) { - const { address, logoURI, name, chainId, AvatarProps, classes } = props - const _chainId = useChainId() + const currentChainId = useChainId() + const { address, logoURI, name, chainId = currentChainId, AvatarProps, classes } = props let _logoURI = logoURI if (!logoURI && isSameAddress(getTokenConstants().NATIVE_TOKEN_ADDRESS, formatEthereumAddress(address))) { - const nativeToken = getChainDetailed(chainId ?? _chainId) + const nativeToken = getChainDetailed(chainId) _logoURI = nativeToken?.nativeCurrency.logoURI } - const { TOKEN_ASSET_BASE_URI } = useTokenAssetBaseURLConstants(chainId ?? _chainId) + const { TOKEN_ASSET_BASE_URI } = useTokenAssetBaseURLConstants(chainId) const fallbackLogos = getFallbackIcons(address, TOKEN_ASSET_BASE_URI ?? []) const tokenBlockie = useBlockie(address) @@ -86,7 +86,7 @@ export const TokenIconUI = memo((props) => { return ( - {name?.substr(0, 1).toLocaleUpperCase()} + {name?.slice(0, 1).toLocaleUpperCase()} ) }) From 7beb20cb08218c33032466fd9130d1a489859c74 Mon Sep 17 00:00:00 2001 From: Septs Date: Mon, 20 Dec 2021 18:26:21 +0800 Subject: [PATCH 15/24] fix: spellcheck --- cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cspell.json b/cspell.json index 3b35105fa48d..45419bda5a94 100644 --- a/cspell.json +++ b/cspell.json @@ -346,7 +346,8 @@ "AICANFT", "CANFT", "Kodav", - "koda" + "koda", + "Enjin" ], "ignoreRegExpList": ["/@servie/"], "overrides": [ From 5ba83d35578f354eefc77112be1927a0713c1de0 Mon Sep 17 00:00:00 2001 From: Septs Date: Wed, 22 Dec 2021 08:00:48 +0800 Subject: [PATCH 16/24] fix: revert NonFungibleAssetProvider --- packages/mask/src/plugins/EVM/index.ts | 1 - .../web3-providers/src/NFTScan/constants.ts | 4 + packages/web3-providers/src/NFTScan/index.ts | 122 +++++++----------- packages/web3-providers/src/opensea/index.ts | 48 ++++--- packages/web3-providers/src/opensea/types.ts | 2 +- packages/web3-providers/src/rarible/index.ts | 11 +- packages/web3-shared/evm/types/index.ts | 6 +- 7 files changed, 90 insertions(+), 104 deletions(-) diff --git a/packages/mask/src/plugins/EVM/index.ts b/packages/mask/src/plugins/EVM/index.ts index 3772d510c874..48ab76cba202 100644 --- a/packages/mask/src/plugins/EVM/index.ts +++ b/packages/mask/src/plugins/EVM/index.ts @@ -3,7 +3,6 @@ import { base } from './base' registerPlugin({ ...base, - SNSAdaptor: { load: () => import('./UI/SNSAdaptor'), hotModuleReload: (hot) => diff --git a/packages/web3-providers/src/NFTScan/constants.ts b/packages/web3-providers/src/NFTScan/constants.ts index 63909d58d99c..ee42f3ffdebe 100644 --- a/packages/web3-providers/src/NFTScan/constants.ts +++ b/packages/web3-providers/src/NFTScan/constants.ts @@ -4,3 +4,7 @@ export const NFTSCAN_ID = 't9k2o5GC' export const NFTSCAN_SECRET = '21da1d638ef5d0bf76e37aa5c2da7fd789ade9e3' export const NFTSCAN_URL = 'https://restapi.nftscan.com' export const NFTSCAN_BASE_API = urlcat(NFTSCAN_URL, '/api/v1') +export const NFTSCAN_ACCESS_TOKEN_URL = urlcat(NFTSCAN_URL, '/gw/token', { + apiKey: NFTSCAN_ID, + apiSecret: NFTSCAN_SECRET, +}) diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 938e8b6e661a..9b18fcb5f353 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -1,43 +1,38 @@ -import { NFTSCAN_BASE_API, NFTSCAN_ID, NFTSCAN_SECRET, NFTSCAN_URL } from './constants' -import type { NFTScanAsset, NFT_Assets } from './types' -import { createERC721ContractDetailed, ChainId, createERC721Token } from '@masknet/web3-shared-evm' -import type { NFTAsset, OrderSide } from '../types' -import urlcat from 'urlcat' +import { ChainId, createERC721ContractDetailed, createERC721Token, ERC721TokenInfo } from '@masknet/web3-shared-evm' import addSeconds from 'date-fns/addSeconds' +import isBefore from 'date-fns/isBefore' +import urlcat from 'urlcat' +import type { NFTAsset, OrderSide } from '../types' +import { NFTSCAN_ACCESS_TOKEN_URL, NFTSCAN_BASE_API } from './constants' +import type { NFTScanAsset, NFT_Assets } from './types' -const tokenCache = new Map<'token', { token: string; expiration: number }>() +const tokenCache = new Map<'token', { token: string; expiration: Date }>() async function getToken() { const token = tokenCache.get('token') - if (token && Date.now() <= token.expiration) return token.token - const requestPath = urlcat(NFTSCAN_URL, '/gw/token', { - apiKey: NFTSCAN_ID, - apiSecret: NFTSCAN_SECRET, - }) - const response = await fetch(requestPath, { mode: 'cors' }) + if (token && isBefore(Date.now(), token.expiration)) { + return token.token + } + const response = await fetch(NFTSCAN_ACCESS_TOKEN_URL, { mode: 'cors' }) interface Payload { data: { accessToken: string; expiration: number } } const { data }: Payload = await response.json() tokenCache.set('token', { token: data.accessToken, - expiration: addSeconds(Date.now(), data.expiration).getTime(), + expiration: addSeconds(Date.now(), data.expiration), }) return data.accessToken } -async function fetchAsset(path: string, config: Partial = {}) { - const response = await fetch(`${NFTSCAN_BASE_API}/${path}`, { +async function fetchAsset(path: string, body?: unknown) { + const response = await fetch(urlcat(NFTSCAN_BASE_API, path), { method: 'POST', - ...config, - headers: { - 'Access-Token': await getToken(), - }, + headers: { 'Access-Token': await getToken() }, + body: JSON.stringify(body), }) - if (!response.ok) return - - return response.json() + return response.json() as Promise<{ data: T }> } function createERC721ContractDetailedFromAssetContract(asset: NFTScanAsset) { @@ -45,85 +40,66 @@ function createERC721ContractDetailedFromAssetContract(asset: NFTScanAsset) { } function createERC721TokenAsset(asset: NFTScanAsset) { - const json = JSON.parse(asset.nft_json) - return createERC721Token( - createERC721ContractDetailedFromAssetContract(asset), - { - name: json.name ?? '', - description: json.description ?? '', - mediaUrl: json.image || '', - owner: asset.nft_holder ?? '', - }, - asset.token_id, - ) + interface Payload { + name?: string + description?: string + image?: string + } + const payload: Payload = JSON.parse(asset.nft_json) + const detailed = createERC721ContractDetailedFromAssetContract(asset) + const info: ERC721TokenInfo = { + name: payload.name ?? '', + description: payload.description ?? '', + mediaUrl: payload.image ?? '', + owner: asset.nft_holder ?? '', + } + return createERC721Token(detailed, info, asset.token_id) } export async function getContractBalance(address: string) { - const response = await fetchAsset('getGroupByNftContract', { - body: JSON.stringify({ - erc: 'erc721', - user_address: address, - }), + const response = await fetchAsset('getGroupByNftContract', { + erc: 'erc721', + user_address: address, }) if (!response) return null - - const { data }: { data: NFT_Assets[] } = response - - return data + return response.data .map((x) => { const contractDetailed = createERC721ContractDetailed( ChainId.Mainnet, x.nft_contract_address, x.nft_platform_name, ) - const balance = x.nft_asset.length - - return { - contractDetailed, - balance, - } + return { contractDetailed, balance } }) .sort((a, b) => a.balance - b.balance) } export async function getNFT(address: string, tokenId: string, chainId: ChainId) { - const response = await fetchAsset('getSingleNft', { - body: JSON.stringify({ - nft_address: address, - token_id: tokenId, - }), + const response = await fetchAsset('getSingleNft', { + nft_address: address, + token_id: tokenId, }) if (!response) return null - - const { data }: { data: NFTScanAsset } = response - - return createERC721TokenAsset(data) + return createERC721TokenAsset(response.data) } export async function getNFTsByPagination(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { const { size = 50, page = 0 } = opts - - const response = await fetchAsset('getAllNftByUserAddress', { - body: JSON.stringify({ - page_size: size, - page_index: page, - use_address: from, - erc: 'erc721', - }), - }) - if (!response) return [] - - type ResponseData = { + interface Payload { content: NFTScanAsset[] page_index: number page_size: number total: number } - - const { data }: { data: ResponseData } = response - - return data.content.map(createERC721TokenAsset) + const response = await fetchAsset('getAllNftByUserAddress', { + page_size: size, + page_index: page, + use_address: from, + erc: 'erc721', + }) + if (!response) return [] + return response.data.content.map(createERC721TokenAsset) } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index d714fdf55674..76384c2c78c3 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -1,11 +1,3 @@ -import type { OpenSeaAssetContract, OpenSeaAssetEvent, OpenSeaResponse, OpenSeaCollection } from './types' -import urlcat from 'urlcat' -import { head, uniqBy } from 'lodash-unified' -import type { AssetCollection, AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' -import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from '../utils' -import fromUnixTime from 'date-fns/fromUnixTime' - -import BigNumber from 'bignumber.js' import { ChainId, createERC721ContractDetailed, @@ -14,8 +6,22 @@ import { ERC721TokenDetailed, EthereumTokenType, } from '@masknet/web3-shared-evm' +import BigNumber from 'bignumber.js' +import fromUnixTime from 'date-fns/fromUnixTime' +import isAfter from 'date-fns/isAfter' +import { head, uniqBy } from 'lodash-unified' +import urlcat from 'urlcat' +import type { AssetCollection, AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' +import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from '../utils' +import type { + OpenSeaAssetContract, + OpenSeaAssetEvent, + OpenSeaCollection, + OpenSeaCustomAccount, + OpenSeaResponse, +} from './types' -const OPENSEA_ACCOUNT_URL = 'https://opensea.io/accounts/' +const OPENSEA_ACCOUNT_URL = 'https://opensea.io/accounts/:address' const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' const OPENSEA_API_URL = 'https://api.opensea.io' @@ -75,7 +81,7 @@ function createERC721TokenAsset( } async function _getAsset(address: string, tokenId: string, chainId: ChainId) { - const requestPath = urlcat(`/api/v1/asset/:address/:tokenId`, { address, tokenId }) + const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) return fetchAsset(requestPath, chainId) } @@ -102,7 +108,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { return { is_verified: ['approved', 'verified'].includes(asset.collection?.safelist_request_status ?? ''), // it's an IOS string as my inspection - is_auction: Date.parse(`${asset.endTime ?? ''}Z`) > Date.now(), + is_auction: isAfter(Date.parse(`${asset.endTime ?? ''}Z`), Date.now()), image_url: asset.image_url_original ?? asset.image_url ?? asset.image_preview_url ?? '', asset_contract: { name: asset.asset_contract.name, @@ -121,11 +127,11 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { current_symbol: desktopOrder?.payment_token_contract?.symbol ?? 'ETH', owner: { ...asset.owner, - link: `${OPENSEA_ACCOUNT_URL}${asset.owner?.user?.username ?? asset.owner.address ?? ''}`, + link: makeAssetLink(asset.owner), }, creator: { ...asset.creator, - link: `${OPENSEA_ACCOUNT_URL}${asset.creator?.user?.username ?? asset.creator?.address ?? ''}`, + link: makeAssetLink(asset.creator), }, token_id: asset.token_id, token_address: asset.token_address, @@ -188,8 +194,6 @@ export async function getHistory(address: string, tokenId: string, chainId: Chai } function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { - const makeLink = (account?: OpenSeaAssetEvent['seller']) => - urlcat(OPENSEA_ACCOUNT_URL, '/:address', { address: account?.user?.username ?? account?.address }) const accountPair = event.event_type === 'successful' ? { @@ -197,13 +201,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.seller?.user?.username, address: event.seller?.address, imageUrl: event.seller?.profile_img_url, - link: makeLink(event.seller), + link: makeAssetLink(event.seller), }, to: { username: event.winner_account?.user?.username, address: event.winner_account?.address, imageUrl: event.winner_account?.profile_img_url, - link: makeLink(event.winner_account), + link: makeAssetLink(event.winner_account), }, } : { @@ -211,13 +215,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.from_account?.user?.username, address: event.from_account?.address, imageUrl: event.from_account?.profile_img_url, - link: makeLink(event.from_account), + link: makeAssetLink(event.from_account), }, to: { username: event.to_account?.user?.username, address: event.to_account?.address, imageUrl: event.to_account?.profile_img_url, - link: makeLink(event.to_account), + link: makeAssetLink(event.to_account), }, } return { @@ -295,3 +299,9 @@ export async function getCollections(address: string, opts: { chainId: ChainId; slug: x.slug, })) } + +function makeAssetLink(account: OpenSeaCustomAccount | undefined) { + return urlcat(OPENSEA_ACCOUNT_URL, { + address: account?.user?.username ?? account?.address, + }) +} diff --git a/packages/web3-providers/src/opensea/types.ts b/packages/web3-providers/src/opensea/types.ts index 6bb5a311d4ea..554650af9934 100644 --- a/packages/web3-providers/src/opensea/types.ts +++ b/packages/web3-providers/src/opensea/types.ts @@ -50,7 +50,7 @@ interface OpenSeaFungibleToken { address: string } -interface OpenSeaCustomAccount { +export interface OpenSeaCustomAccount { address: string profile_img_url: string user?: { diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index deefd2bdb529..2a42d913c871 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -33,11 +33,10 @@ export async function fetchFromRarible(path: string, init?: RequestInit) { mode: 'cors', ...init, }) - return response.json() as Promise } -export async function getProfilesFromRarible(addresses: (string | undefined)[]) { +export function getProfilesFromRarible(addresses: (string | undefined)[]) { return fetchFromRarible('/profiles/list', { method: 'POST', body: JSON.stringify(addresses), @@ -117,7 +116,7 @@ export async function getOrders(tokenAddress: string, tokenId: string, side: Ord } export async function getHistory(tokenAddress: string, tokenId: string): Promise { - let histories = await fetchFromRarible(`/activity`, { + let histories = await fetchFromRarible('/activity', { method: 'POST', body: JSON.stringify({ // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], @@ -262,24 +261,22 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): NFT } } -async function _getAsset(address: string, tokenId: string) { +function _getAsset(address: string, tokenId: string) { const requestPath = urlcat('/v0.1/nft/items/:address::tokenId', { includeMeta: true, address, tokenId, }) - const assetResponse = await fetchFromRarible(requestPath, { + return fetchFromRarible(requestPath, { method: 'GET', mode: 'cors', headers: { 'content-type': 'application/json' }, }) - return assetResponse } export async function getAsset(address: string, tokenId: string, chainId: ChainId) { const asset = await _getAsset(address, tokenId) if (!asset) return - return createNFTAsset(asset, chainId) } diff --git a/packages/web3-shared/evm/types/index.ts b/packages/web3-shared/evm/types/index.ts index d7a67f5ae5a7..e3b0ef9c5cbc 100644 --- a/packages/web3-shared/evm/types/index.ts +++ b/packages/web3-shared/evm/types/index.ts @@ -542,9 +542,9 @@ export enum FungibleAssetProvider { } export enum NonFungibleAssetProvider { - OPENSEA = 0, - RARIBLE = 1, - NFTSCAN = 2, + OPENSEA = 'OpenSea', + RARIBLE = 'Rarible', + NFTSCAN = 'NFTScan', } export type UnboxTransactionObject = T extends NonPayableTransactionObject From 67c07c1254a2c7c599a29adf7545e6f6c3ac9440 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Wed, 22 Dec 2021 18:47:13 +0800 Subject: [PATCH 17/24] fix: settings --- .../extension/popups/pages/Swap/SwapBox/index.tsx | 13 ++----------- .../plugins/Collectible/SNSAdaptor/Collectible.tsx | 6 +++--- .../Collectible/hooks/useCollectibleState.ts | 10 +++++----- packages/mask/src/plugins/Collectible/settings.ts | 4 ++-- .../Trader/SNSAdaptor/trader/FootnoteMenu.tsx | 2 +- .../mask/src/utils/hooks/useSettingSwitcher.tsx | 2 +- 6 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx b/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx index 579f2670092d..b97e2a3def04 100644 --- a/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx +++ b/packages/mask/src/extension/popups/pages/Swap/SwapBox/index.tsx @@ -1,15 +1,11 @@ -import type { TradeProvider } from '@masknet/public-api' import { useChainId } from '@masknet/web3-shared-evm' -import { useCallback, useMemo } from 'react' +import { useMemo } from 'react' import { useHistory, useLocation } from 'react-router-dom' import { useUpdateEffect } from 'react-use' -import { currentTradeProviderSettings } from '../../../../../plugins/Trader/settings' -import type { FootnoteMenuOption } from '../../../../../plugins/Trader/SNSAdaptor/trader/FootnoteMenu' import { Trader } from '../../../../../plugins/Trader/SNSAdaptor/trader/Trader' import { TradeContext, useTradeContext } from '../../../../../plugins/Trader/trader/useTradeContext' -import { useAvailableTraderProviders } from '../../../../../plugins/Trader/trending/useAvailableTraderProviders' import { useCurrentTradeProvider } from '../../../../../plugins/Trader/trending/useCurrentTradeProvider' -import { Coin, TagType } from '../../../../../plugins/Trader/types' +import type { Coin } from '../../../../../plugins/Trader/types' import { PopupRoutes } from '@masknet/shared-base' export function SwapBox() { @@ -18,7 +14,6 @@ export function SwapBox() { const chainId = useChainId() const tradeProvider = useCurrentTradeProvider(chainId) const tradeContext = useTradeContext(tradeProvider) - const { value: tradeProviders = [] } = useAvailableTraderProviders(TagType.CASH, 'MASK') const coin = useMemo(() => { if (!location.search) return undefined @@ -36,10 +31,6 @@ export function SwapBox() { history.replace(PopupRoutes.Swap) }, [chainId]) - const onTradeProviderChange = useCallback((option: FootnoteMenuOption) => { - currentTradeProviderSettings.value = option.value as TradeProvider - }, []) - return ( diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx index 8e1dec1bbc7f..0cda4faed078 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/Collectible.tsx @@ -31,7 +31,7 @@ import { CollectibleCard } from './CollectibleCard' import { CollectibleProviderIcon } from './CollectibleProviderIcon' import { PluginSkeleton } from './PluginSkeleton' import { CollectibleTab } from '../types' -import { currentCollectibleProviderSettings } from '../settings' +import { currentNonFungibleAssetProviderSettings } from '../settings' import { MaskTextIcon } from '../../../resources/MaskIcon' import { resolveAssetLinkOnOpenSea, resolveCollectibleProviderName } from '../pipes' import { ActionBar } from './ActionBar' @@ -151,13 +151,13 @@ export function Collectible(props: CollectibleProps) { //#region sync with settings const collectibleProviderOptions = getEnumAsArray(NonFungibleAssetProvider) const onDataProviderChange = useCallback((option: FootnoteMenuOption) => { - currentCollectibleProviderSettings.value = option.value as NonFungibleAssetProvider + currentNonFungibleAssetProviderSettings.value = option.value as NonFungibleAssetProvider }, []) //#endregion //#region provider switcher const CollectibleProviderSwitcher = useSettingsSwitcher( - currentCollectibleProviderSettings, + currentNonFungibleAssetProviderSettings, getEnumAsArray(NonFungibleAssetProvider).map((x) => x.value), resolveCollectibleProviderName, ) diff --git a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts index 255570434d74..243690c66181 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts @@ -1,7 +1,7 @@ import { useRef, useState } from 'react' import { createContainer } from 'unstated-next' import { OrderSide } from 'opensea-js/lib/types' -import { currentCollectibleProviderSettings } from '../settings' +import { currentNonFungibleAssetProviderSettings } from '../settings' import { CollectibleTab, CollectibleToken } from '../types' import { useAsset, useHistory, useOrders } from '../../EVM/hooks' import { useAssetOrder } from './useAssetOrder' @@ -10,7 +10,7 @@ import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm/types' function useCollectibleState(token?: CollectibleToken) { const [tabIndex, setTabIndex] = useState(CollectibleTab.ARTICLE) - currentCollectibleProviderSettings.value = token?.provider ?? NonFungibleAssetProvider.OPENSEA + currentNonFungibleAssetProviderSettings.value = token?.provider ?? NonFungibleAssetProvider.OPENSEA const asset = useAsset( token?.contractAddress ?? '', @@ -25,7 +25,7 @@ function useCollectibleState(token?: CollectibleToken) { //#region offers const [offerPage, setOfferPage] = useState(0) const offers = useOrders( - currentCollectibleProviderSettings.value, + currentNonFungibleAssetProviderSettings.value, offerPage, 50, tabIndex === CollectibleTab.OFFER ? token?.contractAddress : undefined, @@ -37,7 +37,7 @@ function useCollectibleState(token?: CollectibleToken) { //#region orders const [orderPage, setOrderPage] = useState(0) const orders = useOrders( - currentCollectibleProviderSettings.value, + currentNonFungibleAssetProviderSettings.value, orderPage, 50, tabIndex === CollectibleTab.LISTING ? token?.contractAddress : undefined, @@ -50,7 +50,7 @@ function useCollectibleState(token?: CollectibleToken) { const [eventPage, setEventPage] = useState(0) const cursors = useRef([]) const events = useHistory( - currentCollectibleProviderSettings.value, + currentNonFungibleAssetProviderSettings.value, eventPage, 50, tabIndex === CollectibleTab.HISTORY ? token?.contractAddress : undefined, diff --git a/packages/mask/src/plugins/Collectible/settings.ts b/packages/mask/src/plugins/Collectible/settings.ts index fb0468f81abd..762851cd3360 100644 --- a/packages/mask/src/plugins/Collectible/settings.ts +++ b/packages/mask/src/plugins/Collectible/settings.ts @@ -2,8 +2,8 @@ import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm/types' import { createGlobalSettings } from '../../settings/createSettings' import { PLUGIN_IDENTIFIER } from './constants' -export const currentCollectibleProviderSettings = createGlobalSettings( - `${PLUGIN_IDENTIFIER}+tradeProvider`, +export const currentNonFungibleAssetProviderSettings = createGlobalSettings( + `${PLUGIN_IDENTIFIER}+nonFungibleAssetProvider`, NonFungibleAssetProvider.OPENSEA, { primary: () => '', diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/FootnoteMenu.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/FootnoteMenu.tsx index 6a224ba75763..2a59724c1d77 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/FootnoteMenu.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/FootnoteMenu.tsx @@ -16,7 +16,7 @@ const useStyles = makeStyles()((theme) => ({ export interface FootnoteMenuOption { name: React.ReactNode - value: number + value: string | number } export interface FootnoteMenuProps { diff --git a/packages/mask/src/utils/hooks/useSettingSwitcher.tsx b/packages/mask/src/utils/hooks/useSettingSwitcher.tsx index 4ed88865f9e8..df624d176e97 100644 --- a/packages/mask/src/utils/hooks/useSettingSwitcher.tsx +++ b/packages/mask/src/utils/hooks/useSettingSwitcher.tsx @@ -4,7 +4,7 @@ import { useValueRef } from '@masknet/shared' import ActionButton from '../../extension/options-page/DashboardComponents/ActionButton' import type { InternalSettings } from '../../settings/createSettings' -export function useSettingsSwitcher>( +export function useSettingsSwitcher>( settings: S, options: T[], resolver: (option: T) => string, From 856f53688ee8ae2caa4a88130bc8f7f35c96abcc Mon Sep 17 00:00:00 2001 From: guanbinrui <52657989+guanbinrui@users.noreply.github.com> Date: Thu, 23 Dec 2021 01:12:33 +0800 Subject: [PATCH 18/24] refactor: web3 providers (#5274) * refactor: code style * refactor: web3 providers --- .../Collectible/SNSAdaptor/HistoryTab/Row.tsx | 5 +- .../Collectible/SNSAdaptor/OrderRow.tsx | 5 +- .../mask/src/plugins/Collectible/utils.ts | 15 - .../mask/src/plugins/CryptoartAI/utils.ts | 26 +- .../mask/src/plugins/EVM/hooks/useHistory.ts | 6 +- .../mask/src/plugins/EVM/hooks/useOrders.ts | 11 +- .../src/plugins/EVM/services/Asset/index.ts | 38 +- .../src/plugins/EVM/services/NFT/index.ts | 18 +- .../SNSAdaptor/SelectNftContractDialog.tsx | 12 +- .../Flow/src/apis/getFungibleAssets.ts | 8 +- packages/web3-providers/src/NFTScan/index.ts | 133 +++---- .../web3-providers/src/coingecko/index.ts | 23 +- packages/web3-providers/src/index.ts | 16 +- .../web3-providers/src/opensea/constants.ts | 4 +- packages/web3-providers/src/opensea/index.ts | 269 ++++++------- packages/web3-providers/src/opensea/types.ts | 9 +- .../src/{utils/index.ts => opensea/utils.ts} | 24 +- packages/web3-providers/src/rarible/index.ts | 369 +++++++++--------- packages/web3-providers/src/rarible/pipes.ts | 1 - .../src/rarible/{helpers.ts => utils.ts} | 0 packages/web3-providers/src/types.ts | 304 +++++++++------ packages/web3-shared/evm/utils/token.ts | 1 - 22 files changed, 635 insertions(+), 662 deletions(-) rename packages/web3-providers/src/{utils/index.ts => opensea/utils.ts} (75%) delete mode 100644 packages/web3-providers/src/rarible/pipes.ts rename packages/web3-providers/src/rarible/{helpers.ts => utils.ts} (100%) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx index c45dddf70577..dc567a7b9263 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx @@ -8,8 +8,7 @@ import { useMemo } from 'react' import { CollectibleState } from '../../hooks/useCollectibleState' import { resolveOpenSeaAssetEventType, resolveRaribleAssetEventType } from '../../pipes' import { Account } from '../Account' -import type { NFTHistory } from '@masknet/web3-providers' -import { getOrderUnitPrice } from '@masknet/web3-providers' +import { getOrderUnitPrice, NonFungibleTokenAPI } from '@masknet/web3-providers' import type { OpenSeaAssetEventType } from '../../types/opensea' import type { RaribleEventType } from '../../types' @@ -43,7 +42,7 @@ const useStyles = makeStyles()((theme) => { }) interface Props { - event: NFTHistory + event: NonFungibleTokenAPI.History isDifferenceToken?: boolean } diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index f369e0cb142b..13bae948d564 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -13,8 +13,7 @@ import { isZero } from '@masknet/web3-shared-base' import { CollectibleState } from '../hooks/useCollectibleState' import { Account } from './Account' import { FormattedBalance } from '@masknet/shared' -import { getOrderUnitPrice } from '@masknet/web3-providers' -import type { AssetOrder } from '@masknet/web3-providers' +import { getOrderUnitPrice, NonFungibleTokenAPI } from '@masknet/web3-providers' import formatDistanceToNow from 'date-fns/formatDistanceToNow' import urlcat from 'urlcat' @@ -57,7 +56,7 @@ const useStyles = makeStyles()((theme) => { }) interface IRowProps { - order: AssetOrder + order: NonFungibleTokenAPI.AssetOrder isDifferenceToken?: boolean acceptable?: boolean } diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index 2e5d5393b607..ed9fa8f8ebfc 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -7,8 +7,6 @@ import { rariblePathnameRegexMatcher, } from './constants' import { ChainId, NonFungibleAssetProvider, formatBalance } from '@masknet/web3-shared-evm' -import type { AssetEvent } from './types' -import { multipliedBy } from '@masknet/web3-shared-base' import { WyvernSchemaName } from 'opensea-js/lib/types' export function checkUrl(url: string): boolean { @@ -69,19 +67,6 @@ export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quan return new BigNumber(price).dividedBy(_quantity).decimalPlaces(4, 1) } -export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { - if (!currentPrice || !decimals) return - const price = formatBalance(usdPrice, 0) - const quantity = formatBalance(currentPrice, decimals) - return multipliedBy(price, quantity).decimalPlaces(2, 1) -} - -export function getLastSalePrice(lastSale: AssetEvent | null) { - if (!lastSale?.total_price || !lastSale?.payment_token?.decimals) return - const price = formatBalance(lastSale.total_price, lastSale.payment_token.decimals) - return price -} - export function isWyvernSchemaName(name: unknown): name is WyvernSchemaName { const schemas: unknown[] = [ WyvernSchemaName.ERC20, diff --git a/packages/mask/src/plugins/CryptoartAI/utils.ts b/packages/mask/src/plugins/CryptoartAI/utils.ts index b62843c61b22..ea576212edf5 100644 --- a/packages/mask/src/plugins/CryptoartAI/utils.ts +++ b/packages/mask/src/plugins/CryptoartAI/utils.ts @@ -1,7 +1,5 @@ -import BigNumber from 'bignumber.js' -import type { AssetEvent, Order } from 'opensea-js/lib/types' import { parseURL } from '@masknet/shared-base' -import { ChainId, formatBalance } from '@masknet/web3-shared-evm' +import { ChainId } from '@masknet/web3-shared-evm' import { escapeRegExp } from 'lodash-unified' import { prefixPath, mainNetwork, testNetwork } from './constants' import type { Token } from './types' @@ -39,28 +37,6 @@ export function getAssetInfoFromURL(url?: string) { return { chain_id, creator, token_id, contractAddress } } -export function getOrderUnitPrice(order: Order) { - if (!order.currentPrice || !order.paymentTokenContract?.decimals) return - const price = formatBalance(order.currentPrice, order.paymentTokenContract.decimals) - const quantity = formatBalance(order.quantity, new BigNumber(order.quantity).toString() !== '1' ? 8 : 0) - - return new BigNumber(price).dividedBy(quantity).toFixed(4, 1) -} - -export function getOrderUSDPrice(order: Order) { - if (!order.currentPrice || !order.paymentTokenContract?.decimals) return - const price = formatBalance(order.paymentTokenContract.usdPrice, 0) - const quantity = formatBalance(order.currentPrice, order.paymentTokenContract.decimals) - - return new BigNumber(price).multipliedBy(quantity).toFixed(2, 1) -} - -export function getLastSalePrice(lastSale: AssetEvent | null) { - if (!lastSale?.totalPrice || !lastSale?.paymentToken?.decimals) return - const price = formatBalance(lastSale.totalPrice, lastSale.paymentToken.decimals) - return price -} - export function toTokenIdentifier(token?: Token) { if (!token) return '' return `${token.contractAddress}_${token.tokenId}` diff --git a/packages/mask/src/plugins/EVM/hooks/useHistory.ts b/packages/mask/src/plugins/EVM/hooks/useHistory.ts index 9316c2a129e7..c702951c0ab7 100644 --- a/packages/mask/src/plugins/EVM/hooks/useHistory.ts +++ b/packages/mask/src/plugins/EVM/hooks/useHistory.ts @@ -1,7 +1,7 @@ -import type { NFTHistory } from '@masknet/web3-providers' -import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { useAsyncRetry } from 'react-use' import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' +import type { NonFungibleTokenAPI } from '@masknet/web3-providers' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' export function useHistory( @@ -10,7 +10,7 @@ export function useHistory( size: number, address?: string, tokenId?: string, -): AsyncStateRetry<{ data: NFTHistory[]; hasNextPage: boolean }> { +): AsyncStateRetry<{ data: NonFungibleTokenAPI.History[]; hasNextPage: boolean }> { const chainId = useChainId() return useAsyncRetry(async () => { if (!address || !tokenId) diff --git a/packages/mask/src/plugins/EVM/hooks/useOrders.ts b/packages/mask/src/plugins/EVM/hooks/useOrders.ts index e2fbbdd7a840..e4a4aa3eeb2f 100644 --- a/packages/mask/src/plugins/EVM/hooks/useOrders.ts +++ b/packages/mask/src/plugins/EVM/hooks/useOrders.ts @@ -1,8 +1,7 @@ -import type { AssetOrder } from '@masknet/web3-providers' -import { OrderSide } from '@masknet/web3-providers' -import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { useAsyncRetry } from 'react-use' import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' +import { NonFungibleTokenAPI } from '@masknet/web3-providers' +import { NonFungibleAssetProvider, useChainId } from '@masknet/web3-shared-evm' import { EVM_RPC } from '../messages' export function useOrders( @@ -11,8 +10,8 @@ export function useOrders( size: number, address?: string, tokenId?: string, - side?: OrderSide, -): AsyncStateRetry<{ data: AssetOrder[]; hasNextPage: boolean }> { + side?: NonFungibleTokenAPI.OrderSide, +): AsyncStateRetry<{ data: NonFungibleTokenAPI.AssetOrder[]; hasNextPage: boolean }> { const chainId = useChainId() return useAsyncRetry(async () => { if (!address || !tokenId) @@ -23,7 +22,7 @@ export function useOrders( const orders = await EVM_RPC.getOrders({ address, tokenId, - side: side ?? OrderSide.Sell, + side: side ?? NonFungibleTokenAPI.OrderSide.Sell, chainId, provider, page, diff --git a/packages/mask/src/plugins/EVM/services/Asset/index.ts b/packages/mask/src/plugins/EVM/services/Asset/index.ts index e9dea1ccd985..04c0316b3a76 100644 --- a/packages/mask/src/plugins/EVM/services/Asset/index.ts +++ b/packages/mask/src/plugins/EVM/services/Asset/index.ts @@ -1,8 +1,6 @@ -import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' - import { unreachable } from '@dimensiondev/kit' -import { NFTScanApi, OpenSeaApi, RaribleApi } from '@masknet/web3-providers' -import type { OrderSide } from '@masknet/web3-providers' +import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { NonFungibleTokenAPI, OpenSea, Rarible } from '@masknet/web3-providers' export interface AssetOption { address: string @@ -14,7 +12,7 @@ export interface AssetOption { export interface OrderOption { address: string tokenId: string - side: OrderSide + side: NonFungibleTokenAPI.OrderSide chainId: ChainId provider: NonFungibleAssetProvider page: number @@ -48,11 +46,11 @@ export interface CollectionOption { export async function getAsset(options: AssetOption) { switch (options.provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getAsset(options.address, options.tokenId, options.chainId) + return OpenSea.getAsset(options.address, options.tokenId, { chainId: options.chainId }) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getAsset(options.address, options.tokenId, options.chainId) + return case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getAsset(options.address, options.tokenId, options.chainId) + return Rarible.getAsset(options.address, options.tokenId, { chainId: options.chainId }) default: unreachable(options.provider) } @@ -62,11 +60,15 @@ export async function getOrders(options: OrderOption) { const { provider, address, tokenId, side, chainId, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getOrders(address, tokenId, side, chainId, page, size) + return OpenSea.getOrders(address, tokenId, side, { + chainId, + page, + size, + }) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getOrders(address, tokenId, side, chainId) + return [] case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getOrders(address, tokenId, side, chainId) + return Rarible.getOrders(address, tokenId, side, { chainId }) default: unreachable(provider) } @@ -76,11 +78,11 @@ export async function getListings(options: ListOption) { const { address, tokenId, chainId, provider } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getListings(address, tokenId, chainId) + return [] case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getListings(address, tokenId, chainId) + return [] case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getListings(address, tokenId, chainId) + return Rarible.getListings(address, tokenId, { chainId }) default: unreachable(provider) } @@ -90,11 +92,11 @@ export async function getHistory(options: HistoryOption) { const { address, tokenId, chainId, provider, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getHistory(address, tokenId, chainId, page, size) + return OpenSea.getHistory(address, tokenId, { chainId, page, size }) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getHistory(address, tokenId, chainId) + return [] case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getHistory(address, tokenId) + return Rarible.getHistory(address, tokenId) default: unreachable(provider) } @@ -104,7 +106,7 @@ export async function getCollections(options: CollectionOption) { const { address, chainId, provider, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getCollections(address, { chainId, page, size }) + return OpenSea.getCollections(address, { chainId, page, size }) case NonFungibleAssetProvider.RARIBLE: return [] case NonFungibleAssetProvider.NFTSCAN: diff --git a/packages/mask/src/plugins/EVM/services/NFT/index.ts b/packages/mask/src/plugins/EVM/services/NFT/index.ts index dfed14a17048..29502742b4f9 100644 --- a/packages/mask/src/plugins/EVM/services/NFT/index.ts +++ b/packages/mask/src/plugins/EVM/services/NFT/index.ts @@ -1,4 +1,4 @@ -import { OpenSeaApi, RaribleApi, NFTScanApi } from '@masknet/web3-providers' +import { OpenSea, Rarible, NFTScan } from '@masknet/web3-providers' import { unreachable } from '@dimensiondev/kit' import { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' @@ -33,11 +33,11 @@ export async function getNFT(options: NFTOption) { const { address, tokenId, chainId, provider } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getNFT(address, tokenId, chainId) + return OpenSea.getToken(address, tokenId, chainId) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getNFT(address, tokenId, chainId) + return NFTScan.getToken(address, tokenId, chainId) case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getNFT(address, tokenId) + return Rarible.getToken(address, tokenId) default: unreachable(provider) } @@ -49,7 +49,7 @@ export async function getNFTBalance(options: BalanceOption) { case NonFungibleAssetProvider.RARIBLE: case NonFungibleAssetProvider.OPENSEA: case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getContractBalance(address) + return NFTScan.getContractBalance(address) default: unreachable(provider) @@ -61,7 +61,7 @@ export async function getNFTContract(options: ContractOption) { switch (provider) { case NonFungibleAssetProvider.OPENSEA: case NonFungibleAssetProvider.NFTSCAN: - return OpenSeaApi.getContract(contractAddress, chainId) + return OpenSea.getContract(contractAddress, chainId) case NonFungibleAssetProvider.RARIBLE: return default: @@ -73,11 +73,11 @@ export async function getNFTsByPagination(options: NFTsByPaginationOption) { const { provider, from, chainId, page, size } = options switch (provider) { case NonFungibleAssetProvider.OPENSEA: - return OpenSeaApi.getNFTsByPagination(from, { chainId, page, size }) + return OpenSea.getTokens(from, { chainId, page, size }) case NonFungibleAssetProvider.NFTSCAN: - return NFTScanApi.getNFTsByPagination(from, { chainId, page, size }) + return NFTScan.getTokens(from, { chainId, page, size }) case NonFungibleAssetProvider.RARIBLE: - return RaribleApi.getNFTsByPagination(from, { chainId, page, size }) + return Rarible.getTokens(from, { chainId, page, size }) default: unreachable(provider) } diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx index 2024add899ae..0ffb1dac5b75 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx @@ -22,6 +22,7 @@ import { useERC721ContractDetailed } from '@masknet/web3-shared-evm' import classNames from 'classnames' import { unionBy } from 'lodash-unified' import { useNFTBalance } from '../../EVM/hooks' +import type { NonFungibleTokenAPI } from '@masknet/web3-providers' const useStyles = makeStyles()((theme) => ({ search: { @@ -106,11 +107,6 @@ const useStyles = makeStyles()((theme) => ({ }, })) -type NFTAsset = { - contractDetailed: ERC721ContractDetailed - balance?: number -} - export interface SelectNftContractDialogProps extends withClasses {} export function SelectNftContractDialog(props: SelectNftContractDialogProps) { @@ -207,8 +203,8 @@ export function SelectNftContractDialog(props: SelectNftContractDialogProps) { export interface SearchResultBoxProps extends withClasses { keyword: string - contractList: NFTAsset[] - searchedTokenList: NFTAsset[] + contractList: NonFungibleTokenAPI.ContractBalance[] + searchedTokenList: NonFungibleTokenAPI.ContractBalance[] onSubmit: (contract: ERC721ContractDetailed) => void } @@ -251,7 +247,7 @@ function SearchResultBox(props: SearchResultBoxProps) { } interface ContractListItemProps { - contract: NFTAsset + contract: NonFungibleTokenAPI.ContractBalance onSubmit: (contract: ERC721ContractDetailed) => void } diff --git a/packages/plugins/Flow/src/apis/getFungibleAssets.ts b/packages/plugins/Flow/src/apis/getFungibleAssets.ts index d9f58c984f9b..bc895c1e7770 100644 --- a/packages/plugins/Flow/src/apis/getFungibleAssets.ts +++ b/packages/plugins/Flow/src/apis/getFungibleAssets.ts @@ -1,6 +1,6 @@ import { Pagination, Web3Plugin, CurrencyType } from '@masknet/plugin-infra' import { ChainId, createClient, getTokenConstants } from '@masknet/web3-shared-flow' -import { getTokenPrice } from '@masknet/web3-providers' +import { CoinGecko } from '@masknet/web3-providers' import { createFungibleAsset, createFungibleToken } from '../helpers' import { rightShift } from '@masknet/web3-shared-base' @@ -47,7 +47,7 @@ async function getTokenBalance( async function getAssetFUSD(chainId: ChainId, account: string) { const { FUSD_ADDRESS = '', FUNGIBLE_TOKEN_ADDRESS = '' } = getTokenConstants(chainId) - const price = await getTokenPrice('usd-coin', CurrencyType.USD) + const price = await CoinGecko.getTokenPrice('usd-coin', CurrencyType.USD) const balance = await getTokenBalance(chainId, account, 8, { fungibleTokenAddress: FUNGIBLE_TOKEN_ADDRESS, tokenAddress: FUSD_ADDRESS, @@ -64,7 +64,7 @@ async function getAssetFUSD(chainId: ChainId, account: string) { async function getAssetFLOW(chainId: ChainId, account: string) { const { FLOW_ADDRESS = '', FUNGIBLE_TOKEN_ADDRESS = '' } = getTokenConstants(chainId) - const price = await getTokenPrice('flow', CurrencyType.USD) + const price = await CoinGecko.getTokenPrice('flow', CurrencyType.USD) const balance = await getTokenBalance(chainId, account, 8, { fungibleTokenAddress: FUNGIBLE_TOKEN_ADDRESS, tokenAddress: FLOW_ADDRESS, @@ -82,7 +82,7 @@ async function getAssetFLOW(chainId: ChainId, account: string) { async function getAssetTether(chainId: ChainId, account: string) { const { TETHER_ADDRESS = '', FUNGIBLE_TOKEN_ADDRESS = '' } = getTokenConstants(chainId) - const price = await getTokenPrice('tether', CurrencyType.USD) + const price = await CoinGecko.getTokenPrice('tether', CurrencyType.USD) const balance = await getTokenBalance(chainId, account, 8, { fungibleTokenAddress: FUNGIBLE_TOKEN_ADDRESS, tokenAddress: TETHER_ADDRESS, diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 9b18fcb5f353..04a1bd175994 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -1,8 +1,8 @@ -import { ChainId, createERC721ContractDetailed, createERC721Token, ERC721TokenInfo } from '@masknet/web3-shared-evm' +import { ChainId, createERC721ContractDetailed, createERC721Token } from '@masknet/web3-shared-evm' import addSeconds from 'date-fns/addSeconds' import isBefore from 'date-fns/isBefore' import urlcat from 'urlcat' -import type { NFTAsset, OrderSide } from '../types' +import type { NonFungibleTokenAPI } from '..' import { NFTSCAN_ACCESS_TOKEN_URL, NFTSCAN_BASE_API } from './constants' import type { NFTScanAsset, NFT_Assets } from './types' @@ -14,10 +14,11 @@ async function getToken() { return token.token } const response = await fetch(NFTSCAN_ACCESS_TOKEN_URL, { mode: 'cors' }) - interface Payload { + const { + data, + }: { data: { accessToken: string; expiration: number } - } - const { data }: Payload = await response.json() + } = await response.json() tokenCache.set('token', { token: data.accessToken, expiration: addSeconds(Date.now(), data.expiration), @@ -40,84 +41,66 @@ function createERC721ContractDetailedFromAssetContract(asset: NFTScanAsset) { } function createERC721TokenAsset(asset: NFTScanAsset) { - interface Payload { + const payload: { name?: string description?: string image?: string - } - const payload: Payload = JSON.parse(asset.nft_json) + } = JSON.parse(asset.nft_json) const detailed = createERC721ContractDetailedFromAssetContract(asset) - const info: ERC721TokenInfo = { - name: payload.name ?? '', - description: payload.description ?? '', - mediaUrl: payload.image ?? '', - owner: asset.nft_holder ?? '', - } - return createERC721Token(detailed, info, asset.token_id) + return createERC721Token( + detailed, + { + name: payload.name ?? '', + description: payload.description ?? '', + mediaUrl: payload.image ?? '', + owner: asset.nft_holder ?? '', + }, + asset.token_id, + ) } -export async function getContractBalance(address: string) { - const response = await fetchAsset('getGroupByNftContract', { - erc: 'erc721', - user_address: address, - }) - if (!response) return null - return response.data - .map((x) => { - const contractDetailed = createERC721ContractDetailed( - ChainId.Mainnet, - x.nft_contract_address, - x.nft_platform_name, - ) - const balance = x.nft_asset.length - return { contractDetailed, balance } +export class NFTScanAPI implements NonFungibleTokenAPI.Provider { + async getContractBalance(address: string) { + const response = await fetchAsset('getGroupByNftContract', { + erc: 'erc721', + user_address: address, }) - .sort((a, b) => a.balance - b.balance) -} - -export async function getNFT(address: string, tokenId: string, chainId: ChainId) { - const response = await fetchAsset('getSingleNft', { - nft_address: address, - token_id: tokenId, - }) - if (!response) return null - return createERC721TokenAsset(response.data) -} - -export async function getNFTsByPagination(from: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const { size = 50, page = 0 } = opts - interface Payload { - content: NFTScanAsset[] - page_index: number - page_size: number - total: number + if (!response) return [] + return response.data + .map((x) => { + const contractDetailed = createERC721ContractDetailed( + ChainId.Mainnet, + x.nft_contract_address, + x.nft_platform_name, + ) + const balance = x.nft_asset.length + return { contractDetailed, balance } + }) + .sort((a, b) => a.balance - b.balance) } - const response = await fetchAsset('getAllNftByUserAddress', { - page_size: size, - page_index: page, - use_address: from, - erc: 'erc721', - }) - if (!response) return [] - return response.data.content.map(createERC721TokenAsset) -} -export async function getAsset(address: string, tokenId: string, chainId: ChainId) { - return {} as NFTAsset -} - -export async function getHistory(address: string, tokenId: string, chainId: ChainId) { - return [] -} - -export async function getListings(address: string, tokenId: string, chainId: ChainId) { - return [] -} - -export async function getOrders(address: string, tokenId: string, side: OrderSide, chainId: ChainId) { - return [] -} + async getToken(address: string, tokenId: string, chainId: ChainId) { + const response = await fetchAsset('getSingleNft', { + nft_address: address, + token_id: tokenId, + }) + if (!response) return + return createERC721TokenAsset(response.data) + } -export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - return [] + async getTokens(from: string, { chainId = ChainId.Mainnet, page = 0, size = 50 }: NonFungibleTokenAPI.Options) { + const response = await fetchAsset<{ + content: NFTScanAsset[] + page_index: number + page_size: number + total: number + }>('getAllNftByUserAddress', { + page_size: size, + page_index: page, + use_address: from, + erc: 'erc721', + }) + if (!response) return [] + return response.data.content.map(createERC721TokenAsset) + } } diff --git a/packages/web3-providers/src/coingecko/index.ts b/packages/web3-providers/src/coingecko/index.ts index bde5313c332d..40f40657bfde 100644 --- a/packages/web3-providers/src/coingecko/index.ts +++ b/packages/web3-providers/src/coingecko/index.ts @@ -1,19 +1,16 @@ import type { CurrencyType } from '@masknet/plugin-infra' import { COINGECKO_URL_BASE } from './constants' +import type { PriceAPI } from '..' -export interface CryptoPrice { - [token: string]: { - [key in CurrencyType]?: string +export class CoinGeckoAPI implements PriceAPI.Provider { + async getTokenPrice(tokenId: string, currency: CurrencyType) { + const requestPath = `${COINGECKO_URL_BASE}/simple/price?ids=${tokenId}&vs_currencies=${currency}` + const price = await fetch(requestPath).then((r) => r.json() as Promise) + return price[tokenId] } -} - -export async function getTokenPrice(tokenId: string, currency: CurrencyType) { - const requestPath = `${COINGECKO_URL_BASE}/simple/price?ids=${tokenId}&vs_currencies=${currency}` - const price = await fetch(requestPath).then((r) => r.json() as Promise) - return price[tokenId] -} -export async function getTokensPrice(tokenIds: string[], currency: CurrencyType) { - const requestPath = `${COINGECKO_URL_BASE}/simple/price?ids=${tokenIds}&vs_currencies=${currency}` - return fetch(requestPath).then((r) => r.json() as Promise) + async getTokensPrice(tokenIds: string[], currency: CurrencyType) { + const requestPath = `${COINGECKO_URL_BASE}/simple/price?ids=${tokenIds}&vs_currencies=${currency}` + return fetch(requestPath).then((r) => r.json() as Promise) + } } diff --git a/packages/web3-providers/src/index.ts b/packages/web3-providers/src/index.ts index 690910ad5436..c33e07297f79 100644 --- a/packages/web3-providers/src/index.ts +++ b/packages/web3-providers/src/index.ts @@ -1,8 +1,12 @@ -export * from './coingecko' +import { CoinGeckoAPI } from './coingecko' +import { OpenSeaAPI } from './opensea' +import { RaribleAPI } from './rarible' +import { NFTScanAPI } from './NFTScan' -export * as OpenSeaApi from './opensea' -export * as RaribleApi from './rarible' -export * as NFTScanApi from './NFTScan' - -export * from './utils' export * from './types' +export * from './opensea/utils' + +export const OpenSea = new OpenSeaAPI() +export const Rarible = new RaribleAPI() +export const NFTScan = new NFTScanAPI() +export const CoinGecko = new CoinGeckoAPI() diff --git a/packages/web3-providers/src/opensea/constants.ts b/packages/web3-providers/src/opensea/constants.ts index 336ce12bb910..3ee4d082d912 100644 --- a/packages/web3-providers/src/opensea/constants.ts +++ b/packages/web3-providers/src/opensea/constants.ts @@ -1 +1,3 @@ -export {} +export const OPENSEA_ACCOUNT_URL = 'https://opensea.io/accounts/:address' +export const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' +export const OPENSEA_API_URL = 'https://api.opensea.io' diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 76384c2c78c3..407ea751a77b 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -1,18 +1,18 @@ import { ChainId, - createERC721ContractDetailed, - createERC721Token, - ERC721ContractDetailed, ERC721TokenDetailed, EthereumTokenType, + createERC721Token, + ERC721ContractDetailed, + createERC721ContractDetailed, } from '@masknet/web3-shared-evm' import BigNumber from 'bignumber.js' import fromUnixTime from 'date-fns/fromUnixTime' import isAfter from 'date-fns/isAfter' import { head, uniqBy } from 'lodash-unified' import urlcat from 'urlcat' -import type { AssetCollection, AssetOrder, NFTAsset, NFTAssetOwner, NFTHistory, OrderSide } from '../types' -import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from '../utils' +import type { NonFungibleTokenAPI } from '../types' +import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from './utils' import type { OpenSeaAssetContract, OpenSeaAssetEvent, @@ -20,41 +20,23 @@ import type { OpenSeaCustomAccount, OpenSeaResponse, } from './types' +import { OPENSEA_ACCOUNT_URL, OPENSEA_API_KEY, OPENSEA_API_URL } from './constants' -const OPENSEA_ACCOUNT_URL = 'https://opensea.io/accounts/:address' -const OPENSEA_API_KEY = 'c38fe2446ee34f919436c32db480a2e3' -const OPENSEA_API_URL = 'https://api.opensea.io' - -async function fetchAsset(url: string, chainId: ChainId) { +async function fetchFromOpenSea(url: string, chainId: ChainId) { if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return - const response = await fetch(urlcat(OPENSEA_API_URL, url), { - method: 'GET', - mode: 'cors', - headers: { 'x-api-key': OPENSEA_API_KEY }, - }) - return response.json() as Promise -} - -export async function getNFTsByPagination(from: string, opts: { chainId?: ChainId; page?: number; size?: number }) { - const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts - const requestPath = urlcat('/api/v1/assets', { - owner: from, - offset: page, - limit: size, - }) - const asset = await fetchAsset<{ assets: OpenSeaResponse[] }>(requestPath, chainId) - if (!asset) return [] - - return asset.assets - .filter( - (x: OpenSeaResponse) => - ['non-fungible', 'semi-fungible'].includes(x.asset_contract.type) || - ['ERC721', 'ERC1155'].includes(x.asset_contract.schema_name), - ) - .map((asset: OpenSeaResponse) => createERC721TokenAsset(from, asset.token_id, chainId, asset)) + try { + const response = await fetch(urlcat(OPENSEA_API_URL, url), { + method: 'GET', + mode: 'cors', + headers: { 'x-api-key': OPENSEA_API_KEY }, + }) + return response.json() as Promise + } catch { + return + } } -function createERC721ContractDetailedAsset( +function createERC721ContractFromAssetContract( address: string, chainId: ChainId, assetContract?: OpenSeaAssetContract, @@ -62,14 +44,14 @@ function createERC721ContractDetailedAsset( return createERC721ContractDetailed(chainId, address, assetContract?.name, assetContract?.token_symbol) } -function createERC721TokenAsset( +function createERC721TokenFromAsset( address: string, tokenId: string, chainId: ChainId, - asset?: OpenSeaResponse, + asset: OpenSeaResponse, ): ERC721TokenDetailed { return createERC721Token( - createERC721ContractDetailedAsset(address, chainId, asset?.asset_contract), + createERC721ContractFromAssetContract(address, chainId, asset?.asset_contract), { name: asset?.name ?? asset?.asset_contract.name ?? '', description: asset?.description ?? '', @@ -80,23 +62,13 @@ function createERC721TokenAsset( ) } -async function _getAsset(address: string, tokenId: string, chainId: ChainId) { - const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) - return fetchAsset(requestPath, chainId) -} - -export async function getContract(address: string, chainId: ChainId) { - const requestPath = urlcat('/api/v1/asset_contract/:address', { address }) - const assetContract = await fetchAsset(requestPath, chainId) - return createERC721ContractDetailedAsset(address, chainId, assetContract) -} - -export async function getNFT(address: string, tokenId: string, chainId: ChainId) { - const asset = await _getAsset(address, tokenId, chainId) - return createERC721TokenAsset(address, tokenId, chainId, asset) +function createAssetLink(account: OpenSeaCustomAccount | undefined) { + return urlcat(OPENSEA_ACCOUNT_URL, { + address: account?.user?.username ?? account?.address, + }) } -function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { +function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NonFungibleTokenAPI.Asset { const desktopOrder = head( asset.sell_orders?.sort((a, b) => new BigNumber(getOrderUSDPrice(a.current_price, a.payment_token_contract?.usd_price) ?? 0) @@ -127,11 +99,11 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { current_symbol: desktopOrder?.payment_token_contract?.symbol ?? 'ETH', owner: { ...asset.owner, - link: makeAssetLink(asset.owner), + link: createAssetLink(asset.owner), }, creator: { ...asset.creator, - link: makeAssetLink(asset.creator), + link: createAssetLink(asset.creator), }, token_id: asset.token_id, token_address: asset.token_address, @@ -155,7 +127,7 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { ).filter((x) => x.type === EthereumTokenType.ERC20), slug: asset.collection.slug, desktopOrder, - top_ownerships: asset.top_ownerships.map((x): { owner: NFTAssetOwner } => ({ + top_ownerships: asset.top_ownerships.map((x): { owner: NonFungibleTokenAPI.AssetOwner } => ({ owner: { address: x.owner.address, profile_img_url: x.owner.profile_img_url, @@ -163,37 +135,12 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NFTAsset { link: '', }, })), - collection: asset.collection as unknown as AssetCollection, + collection: asset.collection as unknown as NonFungibleTokenAPI.AssetCollection, response_: asset as any, } } -export async function getAsset(address: string, tokenId: string, chainId: ChainId) { - const asset = await _getAsset(address, tokenId, chainId) - if (!asset) return - - return createNFTAsset(asset, chainId) -} - -export async function getHistory(address: string, tokenId: string, chainId: ChainId, page: number, size: number) { - const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/events', { - asset_contract_address: address, - token_id: tokenId, - offset: page, - limit: size, - }) - const response = await fetch(requestPath, { - mode: 'cors', - headers: { 'x-api-key': OPENSEA_API_KEY }, - }) - interface Payload { - asset_events: OpenSeaAssetEvent[] - } - const { asset_events }: Payload = await response.json() - return asset_events.map(createNFTHistory) -} - -function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { +function createNFTHistory(event: OpenSeaAssetEvent): NonFungibleTokenAPI.History { const accountPair = event.event_type === 'successful' ? { @@ -201,13 +148,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.seller?.user?.username, address: event.seller?.address, imageUrl: event.seller?.profile_img_url, - link: makeAssetLink(event.seller), + link: createAssetLink(event.seller), }, to: { username: event.winner_account?.user?.username, address: event.winner_account?.address, imageUrl: event.winner_account?.profile_img_url, - link: makeAssetLink(event.winner_account), + link: createAssetLink(event.winner_account), }, } : { @@ -215,13 +162,13 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { username: event.from_account?.user?.username, address: event.from_account?.address, imageUrl: event.from_account?.profile_img_url, - link: makeAssetLink(event.from_account), + link: createAssetLink(event.from_account), }, to: { username: event.to_account?.user?.username, address: event.to_account?.address, imageUrl: event.to_account?.profile_img_url, - link: makeAssetLink(event.to_account), + link: createAssetLink(event.to_account), }, } return { @@ -239,69 +186,97 @@ function createNFTHistory(event: OpenSeaAssetEvent): NFTHistory { } } -export async function getListings(address: string, tokenId: string, chainId: ChainId) { - return [] -} +export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { + async getAsset(address: string, tokenId: string, { chainId = ChainId.Mainnet }: { chainId?: ChainId } = {}) { + const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) + const response = await fetchFromOpenSea(requestPath, chainId) + if (!response) return + return createNFTAsset(response, chainId) + } -async function fetchOrders( - chainId: ChainId, - address: string, - tokenId: string, - side: number, - page: number, - size: number, -) { - const requestPath = urlcat(OPENSEA_API_URL, '/wyvern/v1/orders', { - asset_contract_address: address, - token_id: tokenId, - side, - offset: page, - limit: size, - }) - const response = await fetch(requestPath, { - method: 'GET', - mode: 'cors', - headers: { Accept: 'application/json', 'x-api-key': OPENSEA_API_KEY }, - }) - if (!response.ok) return [] - interface Payload { - orders: AssetOrder[] + async getContract(address: string, chainId: ChainId) { + const requestPath = urlcat('/api/v1/asset_contract/:address', { address }) + const assetContract = await fetchFromOpenSea(requestPath, chainId) + return createERC721ContractFromAssetContract(address, chainId, assetContract) } - const payload: Payload = await response.json() - return payload.orders ?? [] -} -export async function getOrders( - address: string, - tokenId: string, - side: OrderSide, - chainId: ChainId, - page: number, - size: number, -) { - return fetchOrders(chainId, address, tokenId, side, page, size) -} + async getToken(address: string, tokenId: string, chainId: ChainId) { + const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) + const response = await fetchFromOpenSea(requestPath, chainId) + if (!response) return + return createERC721TokenFromAsset(address, tokenId, chainId, response) + } -export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/collections', { - address, - offset: opts.page, - limit: opts.size, - }) - const response = await fetch(requestPath, { headers: { 'x-api-key': OPENSEA_API_KEY } }) - interface Payload { - collections: OpenSeaCollection[] + async getTokens(from: string, opts: NonFungibleTokenAPI.Options) { + const { chainId = ChainId.Mainnet, page = 0, size = 50 } = opts + const requestPath = urlcat('/api/v1/assets', { + owner: from, + offset: page, + limit: size, + }) + const response = await fetchFromOpenSea<{ assets: OpenSeaResponse[] }>(requestPath, chainId) + return ( + response?.assets + .filter( + (x: OpenSeaResponse) => + ['non-fungible', 'semi-fungible'].includes(x.asset_contract.type) || + ['ERC721', 'ERC1155'].includes(x.asset_contract.schema_name), + ) + .map((asset: OpenSeaResponse) => createERC721TokenFromAsset(from, asset.token_id, chainId, asset)) ?? [] + ) } - const { collections }: Payload = await response.json() - return collections.map((x) => ({ - name: x.name, - image: x.image_url || undefined, - slug: x.slug, - })) -} -function makeAssetLink(account: OpenSeaCustomAccount | undefined) { - return urlcat(OPENSEA_ACCOUNT_URL, { - address: account?.user?.username ?? account?.address, - }) + async getHistory( + address: string, + tokenId: string, + { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}, + ) { + const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/events', { + asset_contract_address: address, + token_id: tokenId, + offset: page, + limit: size, + }) + const response = await fetchFromOpenSea<{ + asset_events: OpenSeaAssetEvent[] + }>(requestPath, chainId) + return response?.asset_events.map(createNFTHistory) ?? [] + } + + async getOrders( + address: string, + tokenId: string, + side: NonFungibleTokenAPI.OrderSide, + { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}, + ) { + const requestPath = urlcat(OPENSEA_API_URL, '/wyvern/v1/orders', { + asset_contract_address: address, + token_id: tokenId, + side, + offset: page, + limit: size, + }) + const response = await fetchFromOpenSea<{ + orders: NonFungibleTokenAPI.AssetOrder[] + }>(requestPath, chainId) + return response?.orders ?? [] + } + + async getCollections(address: string, { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}) { + const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/collections', { + address, + offset: page, + limit: size, + }) + const response = await fetchFromOpenSea<{ + collections: OpenSeaCollection[] + }>(requestPath, chainId) + return ( + response?.collections.map((x) => ({ + name: x.name, + image: x.image_url || undefined, + slug: x.slug, + })) ?? [] + ) + } } diff --git a/packages/web3-providers/src/opensea/types.ts b/packages/web3-providers/src/opensea/types.ts index 554650af9934..e10c138520b7 100644 --- a/packages/web3-providers/src/opensea/types.ts +++ b/packages/web3-providers/src/opensea/types.ts @@ -1,4 +1,5 @@ -import type { AssetOrder } from '../types' +import type { NonFungibleTokenAPI } from '..' + export interface OpenSeaFees { opensea_seller_fee_basis_points: number opensea_buyer_fee_basis_points: number @@ -86,9 +87,9 @@ export interface OpenSeaResponse extends Asset { name: string description: string owner: OpenSeaCustomAccount - orders: AssetOrder[] | null - buy_orders: AssetOrder[] | null - sell_orders: AssetOrder[] | null + orders: NonFungibleTokenAPI.AssetOrder[] | null + buy_orders: NonFungibleTokenAPI.AssetOrder[] | null + sell_orders: NonFungibleTokenAPI.AssetOrder[] | null is_presale: boolean image_url: string image_preview_url: string diff --git a/packages/web3-providers/src/utils/index.ts b/packages/web3-providers/src/opensea/utils.ts similarity index 75% rename from packages/web3-providers/src/utils/index.ts rename to packages/web3-providers/src/opensea/utils.ts index 54ec708cab57..3784b69500db 100644 --- a/packages/web3-providers/src/utils/index.ts +++ b/packages/web3-providers/src/opensea/utils.ts @@ -1,5 +1,5 @@ -import { ChainId, createERC20Token, createNativeToken, formatBalance } from '@masknet/web3-shared-evm' import BigNumber from 'bignumber.js' +import { ChainId, createERC20Token, createNativeToken, formatBalance } from '@masknet/web3-shared-evm' export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { if (!currentPrice || !decimals || !quantity) return @@ -15,19 +15,15 @@ export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decim return new BigNumber(price).multipliedBy(quantity).decimalPlaces(2, 1) } -export function getLastSalePrice(total_price?: string, decimals?: number) { - if (!total_price || !decimals) return - return formatBalance(total_price, decimals) -} - -interface Token { - address: string - decimals: number - name: string - symbol: string -} - -export function toTokenDetailed(chainId: ChainId, token: Token) { +export function toTokenDetailed( + chainId: ChainId, + token: { + address: string + decimals: number + name: string + symbol: string + }, +) { if (token.symbol === 'ETH') return createNativeToken(chainId) return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) } diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 2a42d913c871..bc31012277c0 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -1,26 +1,26 @@ +import urlcat from 'urlcat' +import { compact, first } from 'lodash-unified' +import { + ChainId, + createLookupTableResolver, + ERC721ContractDetailed, + ERC721TokenDetailed, + EthereumTokenType, + FungibleTokenDetailed, +} from '@masknet/web3-shared-evm' import { Ownership, - RaribleCollectibleResponse, RaribleEventType, RaribleHistory, RaribleNFTItemMapResponse, RaribleOfferResponse, RaribleProfileResponse, } from './types' -import { - ChainId, - createLookupTableResolver, - ERC721TokenDetailed, - EthereumTokenType, - FungibleTokenDetailed, -} from '@masknet/web3-shared-evm' import { RaribleUserURL, RaribleRopstenUserURL, RaribleMainnetURL } from './constants' -import urlcat from 'urlcat' -import { AssetOrder, NFTAsset, NFTHistory, OrderSide } from '../types' -import { compact, first } from 'lodash-unified' -import { toRaribleImage } from './helpers' +import { toRaribleImage } from './utils' +import { NonFungibleTokenAPI } from '..' -export const resolveRaribleUserNetwork = createLookupTableResolver( +const resolveRaribleUserNetwork = createLookupTableResolver( { [ChainId.Mainnet]: RaribleUserURL, [ChainId.Ropsten]: RaribleRopstenUserURL, @@ -28,7 +28,7 @@ export const resolveRaribleUserNetwork = createLookupTableResolver(path: string, init?: RequestInit) { +async function fetchFromRarible(path: string, init?: RequestInit) { const response = await fetch(urlcat(RaribleMainnetURL, path), { mode: 'cors', ...init, @@ -36,7 +36,7 @@ export async function fetchFromRarible(path: string, init?: RequestInit) { return response.json() as Promise } -export function getProfilesFromRarible(addresses: (string | undefined)[]) { +function getProfilesFromRarible(addresses: (string | undefined)[]) { return fetchFromRarible('/profiles/list', { method: 'POST', body: JSON.stringify(addresses), @@ -46,145 +46,7 @@ export function getProfilesFromRarible(addresses: (string | undefined)[]) { }) } -export async function getOffers(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { - const requestPath = urlcat('/items/:tokenAddress::tokenId/offers', { tokenAddress, tokenId }) - const orders = await fetchFromRarible(requestPath, { - method: 'POST', - body: JSON.stringify({ size: 20 }), - headers: { 'content-type': 'application/json' }, - }) - const profiles = await getProfilesFromRarible(orders.map((item) => item.maker)) - return orders.map((order): AssetOrder => { - const ownerInfo = profiles.find((owner) => owner.id === order.maker) - return { - created_time: order.updateDate, - current_price: order.buyPriceEth, - current_bounty: order.fee, - payment_token: order.token, - listing_time: 0, - side: OrderSide.Buy, - quantity: '1', - expiration_time: 0, - order_hash: order.signature, - approved_on_chain: false, - maker_account: { - user: { username: ownerInfo?.name ?? '' }, - address: ownerInfo?.id ?? '', - profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, - }, - } - }) -} - -export async function getListings(tokenAddress: string, tokenId: string, chainId: ChainId): Promise { - const requestPath = urlcat('/items/:tokenAddress::tokenId/ownerships', { tokenAddress, tokenId }) - const assets = await fetchFromRarible(requestPath) - const listings = assets.filter((x) => x.selling) - const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) - return listings.map((asset): AssetOrder => { - const ownerInfo = profiles.find((owner) => owner.id === asset.owner) - return { - created_time: asset.date, - approved_on_chain: false, - current_price: asset.priceEth, - payment_token: asset.token, - listing_time: 0, - side: OrderSide.Buy, - quantity: '1', - expiration_time: 0, - order_hash: asset.signature, - maker_account: { - user: { username: ownerInfo?.name ?? '' }, - address: ownerInfo?.id ?? '', - profile_img_url: toRaribleImage(ownerInfo?.image), - link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, - }, - } - }) -} - -export async function getOrders(tokenAddress: string, tokenId: string, side: OrderSide, chainId: ChainId) { - switch (side) { - case OrderSide.Buy: - return getOffers(tokenAddress, tokenId, chainId) - case OrderSide.Sell: - return getListings(tokenAddress, tokenId, chainId) - default: - return [] - } -} - -export async function getHistory(tokenAddress: string, tokenId: string): Promise { - let histories = await fetchFromRarible('/activity', { - method: 'POST', - body: JSON.stringify({ - // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], - filter: { - '@type': 'by_item', - address: tokenAddress, - tokenId, - }, - size: 100, - }), - headers: { - 'content-type': 'application/json', - }, - }) - - if (histories.length) { - histories = histories.filter((x) => Object.values(RaribleEventType).includes(x['@type'])) - } - - const profiles = await getProfilesFromRarible( - compact([ - ...histories.map((history) => history.owner), - ...histories.map((history) => history.buyer), - ...histories.map((history) => history.from), - ]), - ) - - return histories.map((history) => { - const ownerInfo = profiles.find((profile) => profile.id === history.owner) - const fromInfo = profiles.find((profile) => profile.id === history.buyer || profile.id === history.from) - return { - id: history.id, - eventType: history['@type'], - timestamp: history.date.getTime() ?? 0, - price: { - quantity: '1', - price: history.price, - asset: { - id: fromInfo?.id, - decimals: 0, - image_url: fromInfo?.image, - image_original_url: '', - image_preview_url: '', - asset_contract: { - symbol: fromInfo?.type, - }, - permalink: '', - }, - }, - accountPair: { - from: { - username: fromInfo?.name, - address: fromInfo?.id, - imageUrl: fromInfo?.image, - link: '', - }, - to: { - username: ownerInfo?.name, - address: ownerInfo?.id, - imageUrl: ownerInfo?.image, - link: '', - }, - }, - } as NFTHistory - }) -} - -function createERC721TokenAsset( +function createERC721TokenFromAsset( tokenAddress: string, tokenId: string, asset?: RaribleNFTItemMapResponse, @@ -207,7 +69,7 @@ function createERC721TokenAsset( } } -function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): NFTAsset { +function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): NonFungibleTokenAPI.Asset { const owner = first(asset?.owners) const creator = first(asset?.creators) return { @@ -274,38 +136,175 @@ function _getAsset(address: string, tokenId: string) { }) } -export async function getAsset(address: string, tokenId: string, chainId: ChainId) { - const asset = await _getAsset(address, tokenId) - if (!asset) return - return createNFTAsset(asset, chainId) -} +export class RaribleAPI implements NonFungibleTokenAPI.Provider { + async getAsset(address: string, tokenId: string, { chainId = ChainId.Mainnet }: { chainId?: ChainId } = {}) { + const asset = await _getAsset(address, tokenId) + if (!asset) return + return createNFTAsset(asset, chainId) + } -export async function getNFT(tokenAddress: string, tokenId: string) { - const asset = await _getAsset(tokenAddress, tokenId) - return createERC721TokenAsset(tokenAddress, tokenId, asset) -} + async getToken(tokenAddress: string, tokenId: string) { + const asset = await _getAsset(tokenAddress, tokenId) + return createERC721TokenFromAsset(tokenAddress, tokenId, asset) + } -export async function getNFTsByPagination( - from: string, - opts: { chainId: ChainId; page?: number; size?: number }, -): Promise { - const requestPath = urlcat('/ethereum/nft/items/byOwner', { owner: from, size: opts.size }) - interface Payload { - total: number - continuation: string - items: RaribleNFTItemMapResponse[] + async getTokens(from: string, opts: NonFungibleTokenAPI.Options): Promise { + const requestPath = urlcat('/ethereum/nft/items/byOwner', { owner: from, size: opts.size }) + interface Payload { + total: number + continuation: string + items: RaribleNFTItemMapResponse[] + } + const asset = await fetchFromRarible(requestPath) + if (!asset) return [] + return asset.items.map((asset) => createERC721TokenFromAsset(asset.contract, asset.tokenId, asset)) } - const asset = await fetchFromRarible(requestPath) - if (!asset) return [] - return asset.items.map((asset) => createERC721TokenAsset(asset.contract, asset.tokenId, asset)) -} -export async function getCollections(address: string, opts: { chainId: ChainId; page?: number; size?: number }) { - const requestPath = urlcat('/collections/:address', { address }) - const response = await fetchFromRarible(requestPath, { - method: 'POST', - body: JSON.stringify({ size: 20 }), - headers: { 'content-type': 'application/json' }, - }) - return [response] + async getOffers( + tokenAddress: string, + tokenId: string, + { chainId = ChainId.Mainnet }: NonFungibleTokenAPI.Options = {}, + ): Promise { + const requestPath = urlcat('/items/:tokenAddress::tokenId/offers', { tokenAddress, tokenId }) + const orders = await fetchFromRarible(requestPath, { + method: 'POST', + body: JSON.stringify({ size: 20 }), + headers: { 'content-type': 'application/json' }, + }) + const profiles = await getProfilesFromRarible(orders.map((item) => item.maker)) + return orders.map((order): NonFungibleTokenAPI.AssetOrder => { + const ownerInfo = profiles.find((owner) => owner.id === order.maker) + return { + created_time: order.updateDate, + current_price: order.buyPriceEth, + current_bounty: order.fee, + payment_token: order.token, + listing_time: 0, + side: NonFungibleTokenAPI.OrderSide.Buy, + quantity: '1', + expiration_time: 0, + order_hash: order.signature, + approved_on_chain: false, + maker_account: { + user: { username: ownerInfo?.name ?? '' }, + address: ownerInfo?.id ?? '', + profile_img_url: toRaribleImage(ownerInfo?.image), + link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, + }, + } + }) + } + + async getListings( + tokenAddress: string, + tokenId: string, + { chainId = ChainId.Mainnet }: NonFungibleTokenAPI.Options = {}, + ): Promise { + const requestPath = urlcat('/items/:tokenAddress::tokenId/ownerships', { tokenAddress, tokenId }) + const assets = await fetchFromRarible(requestPath) + const listings = assets.filter((x) => x.selling) + const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) + return listings.map((asset): NonFungibleTokenAPI.AssetOrder => { + const ownerInfo = profiles.find((owner) => owner.id === asset.owner) + return { + created_time: asset.date, + approved_on_chain: false, + current_price: asset.priceEth, + payment_token: asset.token, + listing_time: 0, + side: NonFungibleTokenAPI.OrderSide.Buy, + quantity: '1', + expiration_time: 0, + order_hash: asset.signature, + maker_account: { + user: { username: ownerInfo?.name ?? '' }, + address: ownerInfo?.id ?? '', + profile_img_url: toRaribleImage(ownerInfo?.image), + link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, + }, + } + }) + } + + async getOrders( + tokenAddress: string, + tokenId: string, + side: NonFungibleTokenAPI.OrderSide, + opts: NonFungibleTokenAPI.Options = {}, + ) { + switch (side) { + case NonFungibleTokenAPI.OrderSide.Buy: + return this.getOffers(tokenAddress, tokenId, opts) + case NonFungibleTokenAPI.OrderSide.Sell: + return this.getListings(tokenAddress, tokenId, opts) + default: + return [] + } + } + + async getHistory(tokenAddress: string, tokenId: string): Promise { + const response = await fetchFromRarible('/activity', { + method: 'POST', + body: JSON.stringify({ + // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], + filter: { + '@type': 'by_item', + address: tokenAddress, + tokenId, + }, + size: 100, + }), + headers: { + 'content-type': 'application/json', + }, + }) + + const histories = response.filter((x) => Object.values(RaribleEventType).includes(x['@type'])) + const profiles = await getProfilesFromRarible( + compact([ + ...histories.map((history) => history.owner), + ...histories.map((history) => history.buyer), + ...histories.map((history) => history.from), + ]), + ) + + return histories.map((history) => { + const ownerInfo = profiles.find((profile) => profile.id === history.owner) + const fromInfo = profiles.find((profile) => profile.id === history.buyer || profile.id === history.from) + return { + id: history.id, + eventType: history['@type'], + timestamp: history.date.getTime() ?? 0, + price: { + quantity: '1', + price: history.price, + asset: { + id: fromInfo?.id, + decimals: 0, + image_url: fromInfo?.image, + image_original_url: '', + image_preview_url: '', + asset_contract: { + symbol: fromInfo?.type, + }, + permalink: '', + }, + }, + accountPair: { + from: { + username: fromInfo?.name, + address: fromInfo?.id, + imageUrl: fromInfo?.image, + link: '', + }, + to: { + username: ownerInfo?.name, + address: ownerInfo?.id, + imageUrl: ownerInfo?.image, + link: '', + }, + }, + } as NonFungibleTokenAPI.History + }) + } } diff --git a/packages/web3-providers/src/rarible/pipes.ts b/packages/web3-providers/src/rarible/pipes.ts deleted file mode 100644 index 336ce12bb910..000000000000 --- a/packages/web3-providers/src/rarible/pipes.ts +++ /dev/null @@ -1 +0,0 @@ -export {} diff --git a/packages/web3-providers/src/rarible/helpers.ts b/packages/web3-providers/src/rarible/utils.ts similarity index 100% rename from packages/web3-providers/src/rarible/helpers.ts rename to packages/web3-providers/src/rarible/utils.ts diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts index b7de7aea30b7..9d61ba87748a 100644 --- a/packages/web3-providers/src/types.ts +++ b/packages/web3-providers/src/types.ts @@ -1,137 +1,199 @@ -import type { ERC20TokenDetailed, NativeTokenDetailed } from '@masknet/web3-shared-evm' +import type { CurrencyType } from '@masknet/plugin-infra' +import type { + ChainId, + ERC20TokenDetailed, + ERC721ContractDetailed, + ERC721TokenDetailed, + NativeTokenDetailed, +} from '@masknet/web3-shared-evm' -export enum OrderSide { - Buy = 0, - Sell = 1, -} -export interface NFTAssetOwner { - address: string - profile_img_url?: string - user?: { - username: string +export namespace PriceAPI { + export interface CryptoPrice { + [token: string]: { + [key in CurrencyType]?: string + } } - link: string -} -export interface NFTAssetTrait { - trait_type: string - value: string + export interface Provider { + getTokenPrice( + tokenId: string, + currency: CurrencyType, + ): Promise<{ + [key in CurrencyType]?: string + }> + getTokensPrice(tokenIds: string[], currency: CurrencyType): Promise + } } -export interface AssetToken { - image_url?: string - eth_price?: string - usd_price?: string - name: string - symbol: string - decimals: number - address: string -} +export namespace NonFungibleTokenAPI { + export enum OrderSide { + Buy = 0, + Sell = 1, + } + export interface AssetOwner { + address: string + profile_img_url?: string + user?: { + username: string + } + link: string + } -export interface AssetOrder { - created_time?: string - current_price?: string - current_bounty?: string - maker_account?: NFTAssetOwner - taker_account?: NFTAssetOwner - payment_token?: string - payment_token_contract?: AssetToken - fee_recipient_account?: AssetToken - cancelled_or_finalized?: boolean - marked_invalid?: boolean - approved_on_chain: boolean - listing_time: number - side: number - quantity: string - expiration_time: number - order_hash: string -} + export interface AssetTrait { + trait_type: string + value: string + } -export interface AssetCollection { - name: string - slug: string - editors: string[] - hidden: boolean - featured: boolean - created_date: string - description: string - image_url: string - largeImage_url: string - featured_image_url: string - stats: object - display_data: object - payment_tokens: AssetToken[] - payout_address?: string - trait_stats: NFTAssetTrait - external_link?: string - wiki_link?: string - safelist_request_status: string -} + export interface AssetToken { + image_url?: string + eth_price?: string + usd_price?: string + name: string + symbol: string + decimals: number + address: string + } -export interface NFTAsset { - is_verified: boolean - collection?: AssetCollection - is_auction: boolean - image_url: string - asset_contract: { name: string; description: string; schemaName: string } | null - current_price: number | null - current_symbol: string - owner: NFTAssetOwner | null - creator: NFTAssetOwner | null - token_id: string - token_address: string - traits: NFTAssetTrait[] - safelist_request_status: string - description: string - name: string - collection_name: string - animation_url?: string - end_time: Date | null - order_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] - offer_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] - slug: string | null - desktopOrder?: AssetOrder - top_ownerships: { - owner: NFTAssetOwner - }[] + export interface AssetOrder { + created_time?: string + current_price?: string + current_bounty?: string + maker_account?: AssetOwner + taker_account?: AssetOwner + payment_token?: string + payment_token_contract?: AssetToken + fee_recipient_account?: AssetToken + cancelled_or_finalized?: boolean + marked_invalid?: boolean + approved_on_chain: boolean + listing_time: number + side: number + quantity: string + expiration_time: number + order_hash: string + } - response_: any -} + export interface AssetCollection { + name: string + slug: string + editors: string[] + hidden: boolean + featured: boolean + created_date: string + description: string + image_url: string + largeImage_url: string + featured_image_url: string + stats: object + display_data: object + payment_tokens: AssetToken[] + payout_address?: string + trait_stats: AssetTrait + external_link?: string + wiki_link?: string + safelist_request_status: string + } -export interface NFTHistory { - id: string - accountPair: { - from?: { - username?: string - address?: string - imageUrl?: string - link: string - } - to?: { - username?: string - address?: string - imageUrl?: string - link: string - } + export interface Asset { + is_verified: boolean + collection?: AssetCollection + is_auction: boolean + image_url: string + asset_contract: { name: string; description: string; schemaName: string } | null + current_price: number | null + current_symbol: string + owner: AssetOwner | null + creator: AssetOwner | null + token_id: string + token_address: string + traits: AssetTrait[] + safelist_request_status: string + description: string + name: string + collection_name: string + animation_url?: string + end_time: Date | null + order_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] + offer_payment_tokens: (ERC20TokenDetailed | NativeTokenDetailed)[] + slug: string | null + desktopOrder?: AssetOrder + top_ownerships: { + owner: AssetOwner + }[] + + response_: any } - price?: { - quantity: string - price: string - asset?: { - id: string - decimals: number - image_url: string - image_original_url: string - image_preview_url: string - asset_contract: { - symbol: string + + export interface History { + id: string + accountPair: { + from?: { + username?: string + address?: string + imageUrl?: string + link: string + } + to?: { + username?: string + address?: string + imageUrl?: string + link: string + } + } + price?: { + quantity: string + price: string + asset?: { + id: string + decimals: number + image_url: string + image_original_url: string + image_preview_url: string + asset_contract: { + symbol: string + } + permalink: string } - permalink: string + paymentToken?: AssetToken } - paymentToken?: AssetToken + + eventType: string + transactionBlockExplorerLink?: string + timestamp: number + } + + export interface Collection { + name: string + image?: string + slug: string + } + + export interface ContractBalance { + contractDetailed: ERC721ContractDetailed + balance?: number + } + + export interface Options { + chainId?: ChainId + page?: number + size?: number } - eventType: string - transactionBlockExplorerLink?: string - timestamp: number + export interface Provider { + getContract?: (address: string, chainId: ChainId) => Promise + getContractBalance?: (address: string) => Promise + getAsset?: (address: string, tokenId: string, opts?: { chainId?: ChainId }) => Promise + getToken?: (address: string, tokenId: string, chainId: ChainId) => Promise + getTokens?: (from: string, opts: Options) => Promise + getHistory?: (address: string, tokenId: string, opts?: Options) => Promise + getListings?: (address: string, tokenId: string, opts?: Options) => Promise + getOffers?: (address: string, tokenId: string, opts?: Options) => Promise + getOrders?: ( + address: string, + tokenId: string, + side: NonFungibleTokenAPI.OrderSide, + opts?: Options, + ) => Promise + getCollections?: (address: string, opts?: Options) => Promise + } } diff --git a/packages/web3-shared/evm/utils/token.ts b/packages/web3-shared/evm/utils/token.ts index c113aeed509e..4877a5aea7e5 100644 --- a/packages/web3-shared/evm/utils/token.ts +++ b/packages/web3-shared/evm/utils/token.ts @@ -125,7 +125,6 @@ export function createERC20Tokens( return accumulator }, base) } -//#endregion export function addGasMargin(value: BigNumber.Value, scale = 3000) { return new BigNumber(value).multipliedBy(new BigNumber(10000).plus(scale)).dividedToIntegerBy(10000) From ad4ded661b1b8ca0cc3ec9965f928127ccc13221 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Thu, 23 Dec 2021 01:26:05 +0800 Subject: [PATCH 19/24] refactor: utils --- .../mask/src/plugins/Collectible/helpers.ts | 5 ----- .../mask/src/plugins/Collectible/utils.ts | 7 ------- packages/web3-providers/src/opensea/index.ts | 21 ++++++++++++++++--- packages/web3-providers/src/opensea/utils.ts | 15 +------------ packages/web3-providers/src/rarible/index.ts | 1 - 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/helpers.ts b/packages/mask/src/plugins/Collectible/helpers.ts index 1bdaf9b39912..8225b55bb27a 100644 --- a/packages/mask/src/plugins/Collectible/helpers.ts +++ b/packages/mask/src/plugins/Collectible/helpers.ts @@ -21,11 +21,6 @@ export function toAsset(asset: { tokenId: string; tokenAddress: string; schemaNa } } -export function toTokenDetailed(chainId: ChainId, token: OpenSeaFungibleToken) { - if (token.symbol === 'ETH') return createNativeToken(chainId) - return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) -} - export function toTokenIdentifier(token?: CollectibleToken) { if (!token) return '' return `${token.contractAddress}_${token.tokenId}` diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index ed9fa8f8ebfc..20e1932a0c32 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -60,13 +60,6 @@ export function getAssetInfoFromURL(url?: string) { return } -export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { - if (!currentPrice || !decimals || !quantity) return - const price = formatBalance(currentPrice, decimals) - const _quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) - return new BigNumber(price).dividedBy(_quantity).decimalPlaces(4, 1) -} - export function isWyvernSchemaName(name: unknown): name is WyvernSchemaName { const schemas: unknown[] = [ WyvernSchemaName.ERC20, diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 407ea751a77b..c128c0be48a9 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -5,6 +5,8 @@ import { createERC721Token, ERC721ContractDetailed, createERC721ContractDetailed, + createNativeToken, + createERC20Token, } from '@masknet/web3-shared-evm' import BigNumber from 'bignumber.js' import fromUnixTime from 'date-fns/fromUnixTime' @@ -12,7 +14,7 @@ import isAfter from 'date-fns/isAfter' import { head, uniqBy } from 'lodash-unified' import urlcat from 'urlcat' import type { NonFungibleTokenAPI } from '../types' -import { getOrderUnitPrice, getOrderUSDPrice, toTokenDetailed } from './utils' +import { getOrderUnitPrice, getOrderUSDPrice } from './utils' import type { OpenSeaAssetContract, OpenSeaAssetEvent, @@ -62,6 +64,19 @@ function createERC721TokenFromAsset( ) } +function createTokenDetailed( + chainId: ChainId, + token: { + address: string + decimals: number + name: string + symbol: string + }, +) { + if (token.symbol === 'ETH') return createNativeToken(chainId) + return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) +} + function createAssetLink(account: OpenSeaCustomAccount | undefined) { return urlcat(OPENSEA_ACCOUNT_URL, { address: account?.user?.username ?? account?.address, @@ -119,10 +134,10 @@ function createNFTAsset(asset: OpenSeaResponse, chainId: ChainId): NonFungibleTo ? fromUnixTime(desktopOrder.listing_time) : null, order_payment_tokens: desktopOrder?.payment_token_contract - ? [toTokenDetailed(chainId, desktopOrder.payment_token_contract)] + ? [createTokenDetailed(chainId, desktopOrder.payment_token_contract)] : [], offer_payment_tokens: uniqBy( - asset.collection.payment_tokens.map((x) => toTokenDetailed(chainId, x)), + asset.collection.payment_tokens.map((x) => createTokenDetailed(chainId, x)), (x) => x.address.toLowerCase(), ).filter((x) => x.type === EthereumTokenType.ERC20), slug: asset.collection.slug, diff --git a/packages/web3-providers/src/opensea/utils.ts b/packages/web3-providers/src/opensea/utils.ts index 3784b69500db..bbaaaec415c8 100644 --- a/packages/web3-providers/src/opensea/utils.ts +++ b/packages/web3-providers/src/opensea/utils.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js' -import { ChainId, createERC20Token, createNativeToken, formatBalance } from '@masknet/web3-shared-evm' +import { formatBalance } from '@masknet/web3-shared-evm' export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { if (!currentPrice || !decimals || !quantity) return @@ -14,16 +14,3 @@ export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decim const quantity = formatBalance(currentPrice, decimals) return new BigNumber(price).multipliedBy(quantity).decimalPlaces(2, 1) } - -export function toTokenDetailed( - chainId: ChainId, - token: { - address: string - decimals: number - name: string - symbol: string - }, -) { - if (token.symbol === 'ETH') return createNativeToken(chainId) - return createERC20Token(chainId, token.address, token.decimals, token.name, token.symbol) -} diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index bc31012277c0..9f7b36ffe0c8 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -3,7 +3,6 @@ import { compact, first } from 'lodash-unified' import { ChainId, createLookupTableResolver, - ERC721ContractDetailed, ERC721TokenDetailed, EthereumTokenType, FungibleTokenDetailed, From 0537dba8a4f040e525f2886f56a870f992568c7a Mon Sep 17 00:00:00 2001 From: lelenei Date: Fri, 24 Dec 2021 15:53:27 +0800 Subject: [PATCH 20/24] fix: bugs --- .../Collectible/SNSAdaptor/OrderRow.tsx | 29 +++++-------------- .../mask/src/plugins/EVM/hooks/useOrders.ts | 2 ++ packages/web3-providers/src/opensea/index.ts | 8 ++--- packages/web3-providers/src/rarible/index.ts | 24 +++++++++------ 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index 13bae948d564..5e8cf8d08eb7 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -3,19 +3,13 @@ import BigNumber from 'bignumber.js' import { Avatar, Link, TableCell, TableRow, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { - ChainId, - formatBalance, - NonFungibleAssetProvider, - resolveAddressLinkOnExplorer, -} from '@masknet/web3-shared-evm' +import { formatBalance, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' import { isZero } from '@masknet/web3-shared-base' import { CollectibleState } from '../hooks/useCollectibleState' import { Account } from './Account' import { FormattedBalance } from '@masknet/shared' import { getOrderUnitPrice, NonFungibleTokenAPI } from '@masknet/web3-providers' import formatDistanceToNow from 'date-fns/formatDistanceToNow' -import urlcat from 'urlcat' const useStyles = makeStyles()((theme) => { return { @@ -67,11 +61,10 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { const address = order.maker_account?.user?.username || order.maker_account?.address || '' const link = useMemo(() => { - return provider === NonFungibleAssetProvider.OPENSEA - ? urlcat('https://opensea.io/accounts/:address', { address }) - : order.maker_account?.link - }, [order, provider]) - + return provider === NonFungibleAssetProvider.OPENSEA ? '' : order.maker_account?.link + }, [order, provider, address]) + console.log('+++++++++++++++++++++++') + console.log(order.payment_token ?? '') return ( @@ -90,11 +83,7 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { {provider === NonFungibleAssetProvider.OPENSEA ? ( - + {order.payment_token_contract?.image_url && ( {provider === NonFungibleAssetProvider.OPENSEA ? ( - + {order.payment_token_contract?.image_url && ( (url: string, chainId: ChainId) { const response = await fetch(urlcat(OPENSEA_API_URL, url), { method: 'GET', mode: 'cors', - headers: { 'x-api-key': OPENSEA_API_KEY }, + headers: { 'x-api-key': OPENSEA_API_KEY, Accept: 'application/json' }, }) return response.json() as Promise } catch { @@ -246,7 +246,7 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { tokenId: string, { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}, ) { - const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/events', { + const requestPath = urlcat('/api/v1/events', { asset_contract_address: address, token_id: tokenId, offset: page, @@ -264,7 +264,7 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { side: NonFungibleTokenAPI.OrderSide, { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}, ) { - const requestPath = urlcat(OPENSEA_API_URL, '/wyvern/v1/orders', { + const requestPath = urlcat('/wyvern/v1/orders', { asset_contract_address: address, token_id: tokenId, side, @@ -278,7 +278,7 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { } async getCollections(address: string, { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}) { - const requestPath = urlcat(OPENSEA_API_URL, '/api/v1/collections', { + const requestPath = urlcat('/api/v1/collections', { address, offset: page, limit: size, diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 9f7b36ffe0c8..7704f76847d3 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -15,7 +15,13 @@ import { RaribleOfferResponse, RaribleProfileResponse, } from './types' -import { RaribleUserURL, RaribleRopstenUserURL, RaribleMainnetURL } from './constants' +import { + RaribleUserURL, + RaribleRopstenUserURL, + RaribleMainnetURL, + RaribleChainURL, + RaribleMainnetAPI_URL, +} from './constants' import { toRaribleImage } from './utils' import { NonFungibleTokenAPI } from '..' @@ -27,8 +33,8 @@ const resolveRaribleUserNetwork = createLookupTableResolver(path: string, init?: RequestInit) { - const response = await fetch(urlcat(RaribleMainnetURL, path), { +async function fetchFromRarible(url: string, path: string, init?: RequestInit) { + const response = await fetch(urlcat(url, path), { mode: 'cors', ...init, }) @@ -36,7 +42,7 @@ async function fetchFromRarible(path: string, init?: RequestInit) { } function getProfilesFromRarible(addresses: (string | undefined)[]) { - return fetchFromRarible('/profiles/list', { + return fetchFromRarible(RaribleMainnetURL, '/profiles/list', { method: 'POST', body: JSON.stringify(addresses), headers: { @@ -128,7 +134,7 @@ function _getAsset(address: string, tokenId: string) { address, tokenId, }) - return fetchFromRarible(requestPath, { + return fetchFromRarible(RaribleChainURL, requestPath, { method: 'GET', mode: 'cors', headers: { 'content-type': 'application/json' }, @@ -154,7 +160,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { continuation: string items: RaribleNFTItemMapResponse[] } - const asset = await fetchFromRarible(requestPath) + const asset = await fetchFromRarible(RaribleMainnetAPI_URL, requestPath) if (!asset) return [] return asset.items.map((asset) => createERC721TokenFromAsset(asset.contract, asset.tokenId, asset)) } @@ -165,7 +171,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { { chainId = ChainId.Mainnet }: NonFungibleTokenAPI.Options = {}, ): Promise { const requestPath = urlcat('/items/:tokenAddress::tokenId/offers', { tokenAddress, tokenId }) - const orders = await fetchFromRarible(requestPath, { + const orders = await fetchFromRarible(RaribleMainnetURL, requestPath, { method: 'POST', body: JSON.stringify({ size: 20 }), headers: { 'content-type': 'application/json' }, @@ -200,7 +206,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { { chainId = ChainId.Mainnet }: NonFungibleTokenAPI.Options = {}, ): Promise { const requestPath = urlcat('/items/:tokenAddress::tokenId/ownerships', { tokenAddress, tokenId }) - const assets = await fetchFromRarible(requestPath) + const assets = await fetchFromRarible(RaribleMainnetURL, requestPath) const listings = assets.filter((x) => x.selling) const profiles = await getProfilesFromRarible(listings.map((x) => x.owner)) return listings.map((asset): NonFungibleTokenAPI.AssetOrder => { @@ -242,7 +248,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { } async getHistory(tokenAddress: string, tokenId: string): Promise { - const response = await fetchFromRarible('/activity', { + const response = await fetchFromRarible(RaribleMainnetURL, '/activity', { method: 'POST', body: JSON.stringify({ // types: ['BID', 'BURN', 'BUY', 'CANCEL', 'CANCEL_BID', 'ORDER', 'MINT', 'TRANSFER', 'SALE'], From 1b3e96709cdb425f4b0fadfbe0d3828702174784 Mon Sep 17 00:00:00 2001 From: lelenei Date: Fri, 24 Dec 2021 18:10:27 +0800 Subject: [PATCH 21/24] fix: getOrders API --- .../Collectible/SNSAdaptor/OrderRow.tsx | 33 ++++++++++++++----- .../mask/src/plugins/EVM/hooks/useOrders.ts | 3 +- packages/web3-providers/src/opensea/index.ts | 26 +++++++++++++-- packages/web3-providers/src/opensea/types.ts | 26 +++++++++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx index 5e8cf8d08eb7..dc0fb3b57681 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/OrderRow.tsx @@ -3,13 +3,19 @@ import BigNumber from 'bignumber.js' import { Avatar, Link, TableCell, TableRow, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { formatBalance, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import { + ChainId, + formatBalance, + NonFungibleAssetProvider, + resolveAddressLinkOnExplorer, +} from '@masknet/web3-shared-evm' import { isZero } from '@masknet/web3-shared-base' import { CollectibleState } from '../hooks/useCollectibleState' import { Account } from './Account' import { FormattedBalance } from '@masknet/shared' import { getOrderUnitPrice, NonFungibleTokenAPI } from '@masknet/web3-providers' import formatDistanceToNow from 'date-fns/formatDistanceToNow' +import urlcat from 'urlcat' const useStyles = makeStyles()((theme) => { return { @@ -61,10 +67,11 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { const address = order.maker_account?.user?.username || order.maker_account?.address || '' const link = useMemo(() => { - return provider === NonFungibleAssetProvider.OPENSEA ? '' : order.maker_account?.link + return provider === NonFungibleAssetProvider.OPENSEA + ? urlcat('https://opensea.io/accounts/:address', { address }) + : order.maker_account?.link }, [order, provider, address]) - console.log('+++++++++++++++++++++++') - console.log(order.payment_token ?? '') + return ( @@ -83,7 +90,11 @@ export function OrderRow({ order, isDifferenceToken }: IRowProps) { {provider === NonFungibleAssetProvider.OPENSEA ? ( - + {order.payment_token_contract?.image_url && ( {provider === NonFungibleAssetProvider.OPENSEA ? ( - - {order.payment_token_contract?.image_url && ( + + {order.payment_token_contract?.image_url ? ( {order.payment_token_contract?.symbol} - )} + ) : null} ) : null} {getOrderUnitPrice( order.current_price, order.payment_token_contract?.decimals, order.quantity, - )}{' '} + )?.toString()}{' '} {provider === NonFungibleAssetProvider.OPENSEA ? order.payment_token_contract?.symbol ?? '' : 'ETH'} diff --git a/packages/mask/src/plugins/EVM/hooks/useOrders.ts b/packages/mask/src/plugins/EVM/hooks/useOrders.ts index 72c46fbc901f..39e0b2588d8a 100644 --- a/packages/mask/src/plugins/EVM/hooks/useOrders.ts +++ b/packages/mask/src/plugins/EVM/hooks/useOrders.ts @@ -28,8 +28,7 @@ export function useOrders( page, size, }) - console.log('-------') - console.log(orders) + return { data: orders, hasNextPage: orders.length === size, diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 38f0ece50ca2..147eaea80106 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -18,6 +18,7 @@ import { getOrderUnitPrice, getOrderUSDPrice } from './utils' import type { OpenSeaAssetContract, OpenSeaAssetEvent, + OpenSeaAssetOrder, OpenSeaCollection, OpenSeaCustomAccount, OpenSeaResponse, @@ -201,6 +202,27 @@ function createNFTHistory(event: OpenSeaAssetEvent): NonFungibleTokenAPI.History } } +function createAssetOrder(order: OpenSeaAssetOrder): NonFungibleTokenAPI.AssetOrder { + return { + created_time: order.created_time, + current_price: order.current_price, + current_bounty: order.current_bounty, + maker_account: { ...order.maker, link: '' }, + taker_account: { ...order.taker, link: '' }, + payment_token: order.payment_token, + payment_token_contract: order.payment_token_contract, + fee_recipient_account: order.fee_recipient, + cancelled_or_finalized: order.cancelled || order.finalized, + marked_invalid: order.marked_invalid, + approved_on_chain: order.approved_on_chain, + listing_time: order.listing_time, + side: order.side, + quantity: order.quantity, + expiration_time: order.expiration_time, + order_hash: order.order_hash, + } +} + export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { async getAsset(address: string, tokenId: string, { chainId = ChainId.Mainnet }: { chainId?: ChainId } = {}) { const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) @@ -272,9 +294,9 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { limit: size, }) const response = await fetchFromOpenSea<{ - orders: NonFungibleTokenAPI.AssetOrder[] + orders: OpenSeaAssetOrder[] }>(requestPath, chainId) - return response?.orders ?? [] + return response?.orders?.map(createAssetOrder) ?? [] } async getCollections(address: string, { chainId = ChainId.Mainnet, page, size }: NonFungibleTokenAPI.Options = {}) { diff --git a/packages/web3-providers/src/opensea/types.ts b/packages/web3-providers/src/opensea/types.ts index e10c138520b7..64b57d83c97d 100644 --- a/packages/web3-providers/src/opensea/types.ts +++ b/packages/web3-providers/src/opensea/types.ts @@ -172,3 +172,29 @@ export interface OpenSeaAssetEvent { } created_date: string } + +export interface OpenSeaAssetOrder { + approved_on_chain: boolean + asset: OpenSeaResponse + listing_time: number + created_time?: string + + current_price?: string + current_bounty?: string + maker: OpenSeaCustomAccount + taker: OpenSeaCustomAccount + + payment_token?: string + payment_token_contract?: NonFungibleTokenAPI.AssetToken + fee_recipient?: NonFungibleTokenAPI.AssetToken + + cancelled?: boolean + finalized?: boolean + + marked_invalid?: boolean + + side: number + quantity: string + expiration_time: number + order_hash: string +} From 35c4127ad2101d8a4e11e28c1c5615ab413bcf97 Mon Sep 17 00:00:00 2001 From: lelenei Date: Fri, 24 Dec 2021 21:41:44 +0800 Subject: [PATCH 22/24] fix: getHistory API --- .../Collectible/SNSAdaptor/HistoryTab/Row.tsx | 6 ++++- .../SNSAdaptor/HistoryTab/index.tsx | 2 ++ .../SNSAdaptor/MakeOfferDialog.tsx | 3 +++ .../Collectible/SNSAdaptor/PostInspector.tsx | 1 - .../Collectible/hooks/useCollectibleState.ts | 25 ++++++++----------- .../src/plugins/Collectible/types/index.ts | 4 +-- .../mask/src/plugins/Collectible/utils.ts | 5 +--- .../mask/src/plugins/EVM/hooks/useHistory.ts | 4 ++- packages/web3-providers/src/opensea/index.ts | 1 + 9 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx index dc567a7b9263..ebf7980846b2 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx @@ -52,7 +52,11 @@ export function Row({ event, isDifferenceToken }: Props) { const unitPrice = useMemo(() => { if (provider === NonFungibleAssetProvider.RARIBLE || !isDifferenceToken || !event.price) return null - return getOrderUnitPrice(event.price.price ?? 0, event.price.paymentToken?.decimals, event.price.quantity) + return getOrderUnitPrice( + event.price.price ?? 0, + event.price.paymentToken?.decimals, + event.price.quantity, + )?.toString() }, [event, isDifferenceToken, provider]) return ( diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx index ac7182414c8b..5557cfebbdcc 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/index.tsx @@ -44,6 +44,8 @@ export function HistoryTab(props: HistoryTabProps) { const { classes } = useStyles() const { provider, events, eventPage, setEventPage } = CollectibleState.useContainer() + console.log('000000000') + console.log(events) //#region If there is a different asset, the unit price and quantity should be displayed const isDifferenceToken = useMemo(() => { if (provider === NonFungibleAssetProvider.OPENSEA) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index d25239d5cf5d..5e6f3c989166 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -75,6 +75,9 @@ export interface MakeOfferDialogProps { export function MakeOfferDialog(props: MakeOfferDialogProps) { const { asset, open, onClose } = props + + console.log('--------------------') + console.log(asset) const isAuction = asset?.value?.is_auction ?? false const isVerified = asset?.value?.is_verified ?? false const leastPrice = diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx index 95138445b06d..bad1491c86bd 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostInspector.tsx @@ -17,7 +17,6 @@ export function PostInspector(props: PostInspectorProps) { chainId: token.chain_id, tokenId: token.token_id, contractAddress: token.address, - provider: token.provider, }}> diff --git a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts index 243690c66181..c2292c6d95b7 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useCollectibleState.ts @@ -1,31 +1,27 @@ -import { useRef, useState } from 'react' +import { useState } from 'react' import { createContainer } from 'unstated-next' import { OrderSide } from 'opensea-js/lib/types' -import { currentNonFungibleAssetProviderSettings } from '../settings' import { CollectibleTab, CollectibleToken } from '../types' import { useAsset, useHistory, useOrders } from '../../EVM/hooks' import { useAssetOrder } from './useAssetOrder' -import { NonFungibleAssetProvider } from '@masknet/web3-shared-evm/types' +import { useValueRef } from '@masknet/shared' +import { currentNonFungibleAssetProviderSettings } from '../settings' function useCollectibleState(token?: CollectibleToken) { const [tabIndex, setTabIndex] = useState(CollectibleTab.ARTICLE) - currentNonFungibleAssetProviderSettings.value = token?.provider ?? NonFungibleAssetProvider.OPENSEA + const provider = useValueRef(currentNonFungibleAssetProviderSettings) - const asset = useAsset( - token?.contractAddress ?? '', - token?.tokenId ?? '', - token?.provider ?? NonFungibleAssetProvider.OPENSEA, - ) + const asset = useAsset(token?.contractAddress ?? '', token?.tokenId ?? '', provider) //#region asset order from sdk - const assetOrder = useAssetOrder(token?.provider ?? NonFungibleAssetProvider.OPENSEA, token) + const assetOrder = useAssetOrder(provider, token) //#endregion //#region offers const [offerPage, setOfferPage] = useState(0) const offers = useOrders( - currentNonFungibleAssetProviderSettings.value, + provider, offerPage, 50, tabIndex === CollectibleTab.OFFER ? token?.contractAddress : undefined, @@ -37,7 +33,7 @@ function useCollectibleState(token?: CollectibleToken) { //#region orders const [orderPage, setOrderPage] = useState(0) const orders = useOrders( - currentNonFungibleAssetProviderSettings.value, + provider, orderPage, 50, tabIndex === CollectibleTab.LISTING ? token?.contractAddress : undefined, @@ -48,9 +44,8 @@ function useCollectibleState(token?: CollectibleToken) { //#region events const [eventPage, setEventPage] = useState(0) - const cursors = useRef([]) const events = useHistory( - currentNonFungibleAssetProviderSettings.value, + provider, eventPage, 50, tabIndex === CollectibleTab.HISTORY ? token?.contractAddress : undefined, @@ -61,7 +56,7 @@ function useCollectibleState(token?: CollectibleToken) { return { token, asset, - provider: token?.provider ?? NonFungibleAssetProvider.OPENSEA, + provider, assetOrder, diff --git a/packages/mask/src/plugins/Collectible/types/index.ts b/packages/mask/src/plugins/Collectible/types/index.ts index ce43c7bbf643..42749043aa32 100644 --- a/packages/mask/src/plugins/Collectible/types/index.ts +++ b/packages/mask/src/plugins/Collectible/types/index.ts @@ -1,5 +1,5 @@ import type { WyvernSchemaName } from 'opensea-js/lib/types' -import type { ChainId, NonFungibleAssetProvider } from '@masknet/web3-shared-evm' +import type { ChainId } from '@masknet/web3-shared-evm' export * from './opensea' export * from './rarible' @@ -8,7 +8,6 @@ export interface CollectibleJSON_Payload { chain_id: ChainId address: string token_id: string - provider: NonFungibleAssetProvider } export enum CollectibleTab { @@ -24,5 +23,4 @@ export interface CollectibleToken { tokenId: string contractAddress: string schemaName?: WyvernSchemaName - provider: NonFungibleAssetProvider } diff --git a/packages/mask/src/plugins/Collectible/utils.ts b/packages/mask/src/plugins/Collectible/utils.ts index 20e1932a0c32..bbb9020c9a7f 100644 --- a/packages/mask/src/plugins/Collectible/utils.ts +++ b/packages/mask/src/plugins/Collectible/utils.ts @@ -1,4 +1,3 @@ -import BigNumber from 'bignumber.js' import { parseURL } from '@masknet/shared-base' import { openseaHostnames, @@ -6,7 +5,7 @@ import { raribleHostnames, rariblePathnameRegexMatcher, } from './constants' -import { ChainId, NonFungibleAssetProvider, formatBalance } from '@masknet/web3-shared-evm' +import { ChainId } from '@masknet/web3-shared-evm' import { WyvernSchemaName } from 'opensea-js/lib/types' export function checkUrl(url: string): boolean { @@ -35,7 +34,6 @@ export function getAssetInfoFromURL(url?: string) { chain_id: _url.host.includes('testnets') ? ChainId.Rinkeby : ChainId.Mainnet, address: openSeaMatched[1], token_id: openSeaMatched[2], - provider: NonFungibleAssetProvider.OPENSEA, } } //#endregion @@ -51,7 +49,6 @@ export function getAssetInfoFromURL(url?: string) { : ChainId.Mainnet, address: raribleMatched[1], token_id: raribleMatched[2], - provider: NonFungibleAssetProvider.RARIBLE, } } //#endregion diff --git a/packages/mask/src/plugins/EVM/hooks/useHistory.ts b/packages/mask/src/plugins/EVM/hooks/useHistory.ts index c702951c0ab7..6764060c7704 100644 --- a/packages/mask/src/plugins/EVM/hooks/useHistory.ts +++ b/packages/mask/src/plugins/EVM/hooks/useHistory.ts @@ -18,10 +18,12 @@ export function useHistory( data: [], hasNextPage: false, } + const histories = await EVM_RPC.getHistory({ address, tokenId, chainId, provider, page, size }) + return { data: histories, hasNextPage: histories.length === size, } - }, [address, tokenId]) + }, [address, tokenId, chainId, provider, page, size]) } diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 147eaea80106..484f409ee77f 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -79,6 +79,7 @@ function createTokenDetailed( } function createAssetLink(account: OpenSeaCustomAccount | undefined) { + if (!account) return '' return urlcat(OPENSEA_ACCOUNT_URL, { address: account?.user?.username ?? account?.address, }) From e4489e21cb98bf5bd7440502a3a54f1a10bcac54 Mon Sep 17 00:00:00 2001 From: lelenei Date: Mon, 27 Dec 2021 20:13:55 +0800 Subject: [PATCH 23/24] fix: use BigNumber --- packages/web3-providers/src/opensea/utils.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/web3-providers/src/opensea/utils.ts b/packages/web3-providers/src/opensea/utils.ts index bbaaaec415c8..0c8f9da58f50 100644 --- a/packages/web3-providers/src/opensea/utils.ts +++ b/packages/web3-providers/src/opensea/utils.ts @@ -1,16 +1,15 @@ import BigNumber from 'bignumber.js' -import { formatBalance } from '@masknet/web3-shared-evm' +import { pow10 } from '@masknet/web3-shared-base' export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { if (!currentPrice || !decimals || !quantity) return - currentPrice = formatBalance(currentPrice, decimals) - quantity = formatBalance(quantity, new BigNumber(quantity).toString() !== '1' ? 8 : 0) - return new BigNumber(currentPrice).dividedBy(quantity).decimalPlaces(4, 1) + const _currentPrice = new BigNumber(currentPrice).div(pow10(decimals)) + const _quantity = new BigNumber(quantity).div(pow10(new BigNumber(quantity).toString() !== '1' ? 8 : 0)) + return _currentPrice.dividedBy(_quantity).decimalPlaces(4, 1) } export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { if (!currentPrice || !decimals) return - const price = formatBalance(usdPrice, 0) - const quantity = formatBalance(currentPrice, decimals) - return new BigNumber(price).multipliedBy(quantity).decimalPlaces(2, 1) + const quantity = new BigNumber(currentPrice).div(pow10(decimals)) + return new BigNumber(usdPrice).multipliedBy(quantity).decimalPlaces(2, 1) } From 8da7b5fe0bf58b17167f45eadde99271f6ece049 Mon Sep 17 00:00:00 2001 From: lelenei Date: Mon, 27 Dec 2021 20:40:43 +0800 Subject: [PATCH 24/24] fix: getOrderUnitPrice --- .../src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx | 1 + .../src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx | 2 -- packages/web3-providers/src/opensea/utils.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx index ebf7980846b2..a22391604bb0 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/HistoryTab/Row.tsx @@ -52,6 +52,7 @@ export function Row({ event, isDifferenceToken }: Props) { const unitPrice = useMemo(() => { if (provider === NonFungibleAssetProvider.RARIBLE || !isDifferenceToken || !event.price) return null + return getOrderUnitPrice( event.price.price ?? 0, event.price.paymentToken?.decimals, diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index 08d51bce18d5..bb554596441c 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -76,8 +76,6 @@ export interface MakeOfferDialogProps { export function MakeOfferDialog(props: MakeOfferDialogProps) { const { asset, open, onClose } = props - console.log('--------------------') - console.log(asset) const isAuction = asset?.value?.is_auction ?? false const isVerified = asset?.value?.is_verified ?? false const leastPrice = diff --git a/packages/web3-providers/src/opensea/utils.ts b/packages/web3-providers/src/opensea/utils.ts index 0c8f9da58f50..ff4529630aaf 100644 --- a/packages/web3-providers/src/opensea/utils.ts +++ b/packages/web3-providers/src/opensea/utils.ts @@ -4,12 +4,12 @@ import { pow10 } from '@masknet/web3-shared-base' export function getOrderUnitPrice(currentPrice?: string, decimals?: number, quantity?: string) { if (!currentPrice || !decimals || !quantity) return const _currentPrice = new BigNumber(currentPrice).div(pow10(decimals)) - const _quantity = new BigNumber(quantity).div(pow10(new BigNumber(quantity).toString() !== '1' ? 8 : 0)) + const _quantity = new BigNumber(quantity).div(pow10(new BigNumber(quantity).eq(1) ? 0 : 8)) return _currentPrice.dividedBy(_quantity).decimalPlaces(4, 1) } export function getOrderUSDPrice(currentPrice?: string, usdPrice?: string, decimals?: number) { if (!currentPrice || !decimals) return const quantity = new BigNumber(currentPrice).div(pow10(decimals)) - return new BigNumber(usdPrice).multipliedBy(quantity).decimalPlaces(2, 1) + return new BigNumber(usdPrice ?? 0).multipliedBy(quantity).decimalPlaces(2, 1) }