diff --git a/packages/dashboard/stories/components/Wallet/CollectibleCard.tsx b/packages/dashboard/stories/components/Wallet/CollectibleCard.tsx index 93799c26ac15..cc9dc776ecd3 100644 --- a/packages/dashboard/stories/components/Wallet/CollectibleCard.tsx +++ b/packages/dashboard/stories/components/Wallet/CollectibleCard.tsx @@ -22,6 +22,7 @@ export const CollectibleCard = of({ mediaType: '', }, contract: { + id: 'address', address: 'address', chainId: 1, name: '', diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts index af4a0009ac65..0acd4f50874f 100644 --- a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts +++ b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts @@ -189,7 +189,11 @@ export const getNonFungibleTokenFn = name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`, description: x.info.description ?? '', owner: x.info.owner, - contract: { ...x.contractDetailed, type: TokenType.NonFungible }, + contract: { + ...x.contractDetailed, + type: TokenType.NonFungible, + id: x.contractDetailed.address, + }, metadata: { name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`, description: x.info.description ?? '', diff --git a/packages/plugin-infra/src/web3-types.ts b/packages/plugin-infra/src/web3-types.ts index dfa026805014..d851716caf51 100644 --- a/packages/plugin-infra/src/web3-types.ts +++ b/packages/plugin-infra/src/web3-types.ts @@ -163,11 +163,13 @@ export declare namespace Web3Plugin { } export interface NonFungibleContract { + id: string chainId: number name: string symbol: string address: string iconURL?: string + balance?: number } export interface FungibleTokenMetadata { diff --git a/packages/provider-proxy/package.json b/packages/provider-proxy/package.json index b5ea09a9fada..46809486d768 100644 --- a/packages/provider-proxy/package.json +++ b/packages/provider-proxy/package.json @@ -17,6 +17,7 @@ "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.0.6", + "@rollup/plugin-replace": "^3.0.1", "@rollup/plugin-sucrase": "^4.0.1", "date-fns": "^2.27.0", "rollup": "^2.60.1", diff --git a/packages/provider-proxy/rollup.config.js b/packages/provider-proxy/rollup.config.js index 0c01fd5525f2..1e9c6e55a578 100644 --- a/packages/provider-proxy/rollup.config.js +++ b/packages/provider-proxy/rollup.config.js @@ -4,6 +4,7 @@ import sucrase from '@rollup/plugin-sucrase' import json from '@rollup/plugin-json' import alias from '@rollup/plugin-alias' import dts from 'rollup-plugin-dts' +import replace from '@rollup/plugin-replace' const config = { input: './src/index.ts', @@ -22,6 +23,9 @@ const config = { ], }), sucrase({ transforms: ['typescript', 'jsx'] }), + replace({ + 'process.env.PROVIDER_API_ENV': JSON.stringify('proxy'), + }), ], external: (id) => { if (id.startsWith('.')) return false diff --git a/packages/provider-proxy/src/producers/nonFungibleCollectionAsset.ts b/packages/provider-proxy/src/producers/nonFungibleCollectionAsset.ts index 5dbb53dfc5dd..f04ee2632978 100644 --- a/packages/provider-proxy/src/producers/nonFungibleCollectionAsset.ts +++ b/packages/provider-proxy/src/producers/nonFungibleCollectionAsset.ts @@ -1,19 +1,14 @@ -import { getOpenSeaCollectionList } from '@masknet/web3-providers' -import type { ProducerArgBase, ProducerKeyFunction, ProducerPushFunction, RPCMethodRegistrationValue } from '../types' +import type { Web3Plugin } from '@masknet/plugin-infra' +import { getNFTScanNFTList, getOpenSeaCollectionList } from '@masknet/web3-providers' import { collectAllPageDate } from '../helper/request' - -interface Collection { - name: string - image?: string - slug: string -} +import type { ProducerArgBase, ProducerKeyFunction, ProducerPushFunction, RPCMethodRegistrationValue } from '../types' interface NonFungibleCollectibleAssetArgs extends ProducerArgBase { address: string } const nonFungibleCollectionAsset = async ( - push: ProducerPushFunction, + push: ProducerPushFunction, getKeys: ProducerKeyFunction, args: NonFungibleCollectibleAssetArgs, ): Promise => { @@ -21,17 +16,30 @@ const nonFungibleCollectionAsset = async ( const openSeaApiKey = await getKeys('opensea') const pageSize = 50 - const collectFromOpenSea = await collectAllPageDate( + const collectionsFromNFTScan = await getNFTScanNFTList(address) + await push( + collectionsFromNFTScan.map((x) => ({ + id: x.contractDetailed.address, + chainId: x.contractDetailed.chainId, + name: x.contractDetailed.name, + symbol: x.contractDetailed.symbol, + address: x.contractDetailed.address, + iconURL: x.contractDetailed.iconURL, + balance: x.balance, + })), + ) + + const collectionsFromOpenSea = await collectAllPageDate( (page: number) => getOpenSeaCollectionList(openSeaApiKey, address, page, pageSize), pageSize, ) - await push(collectFromOpenSea) + await push(collectionsFromOpenSea) } -const producer: RPCMethodRegistrationValue = { +const producer: RPCMethodRegistrationValue = { method: 'mask.fetchNonFungibleCollectionAsset', producer: nonFungibleCollectionAsset, - distinctBy: (item) => item.name, + distinctBy: (item) => item.address, } export default producer diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index 3bfb6fe3faf1..6683679d98c9 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -5,6 +5,7 @@ import urlcat from 'urlcat' import type { NonFungibleTokenAPI } from '..' import { NFTSCAN_ACCESS_TOKEN_URL, NFTSCAN_BASE_API } from './constants' import type { NFTScanAsset, NFT_Assets } from './types' +import { isProxyENV } from '../helpers' const tokenCache = new Map<'token', { token: string; expiration: Date }>() @@ -13,7 +14,7 @@ async function getToken() { if (token && isBefore(Date.now(), token.expiration)) { return token.token } - const response = await fetch(NFTSCAN_ACCESS_TOKEN_URL, { mode: 'cors' }) + const response = await fetch(NFTSCAN_ACCESS_TOKEN_URL, { ...(!isProxyENV && { mode: 'cors' }) }) const { data, }: { @@ -116,3 +117,8 @@ export class NFTScanAPI implements NonFungibleTokenAPI.Provider { } } } + +export function getNFTScanNFTList(address: string) { + const nftScanAPI = new NFTScanAPI() + return nftScanAPI.getContractBalance(address) +} diff --git a/packages/web3-providers/src/helpers.ts b/packages/web3-providers/src/helpers.ts index b4f0bf6359fd..818ef61b5e26 100644 --- a/packages/web3-providers/src/helpers.ts +++ b/packages/web3-providers/src/helpers.ts @@ -1,3 +1,5 @@ +export const isProxyENV = process.env.PROVIDER_API_ENV === 'proxy' + export async function fetchJSON(requestInfo: RequestInfo, requestInit?: RequestInit): Promise { const res = await globalThis.fetch(requestInfo, requestInit) return res.json() diff --git a/packages/web3-providers/src/index.ts b/packages/web3-providers/src/index.ts index d164b0078261..1555b44b07da 100644 --- a/packages/web3-providers/src/index.ts +++ b/packages/web3-providers/src/index.ts @@ -21,3 +21,4 @@ export const KeyValue = new KeyValueAPI() export { getOpenSeaNFTList, getOpenSeaCollectionList } from './opensea' export { getAssetListFromDebank } from './debank' export { getRaribleNFTList } from './rarible' +export { getNFTScanNFTList } from './NFTScan' diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index b42c6c0db191..a0945570b110 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -13,7 +13,7 @@ import fromUnixTime from 'date-fns/fromUnixTime' import isAfter from 'date-fns/isAfter' import { head, uniqBy } from 'lodash-unified' import urlcat from 'urlcat' -import { NonFungibleTokenAPI } from '../types' +import type { NonFungibleTokenAPI } from '../types' import { getOrderUnitPrice, getOrderUSDPrice } from './utils' import type { OpenSeaAssetContract, @@ -24,15 +24,16 @@ import type { OpenSeaResponse, } from './types' import { OPENSEA_ACCOUNT_URL, OPENSEA_API_KEY, OPENSEA_API_URL } from './constants' +import { isProxyENV } from '../helpers' -async function fetchFromOpenSea(url: string, chainId: ChainId, apiKey?: string, env?: NonFungibleTokenAPI.APIEnv) { +async function fetchFromOpenSea(url: string, chainId: ChainId, apiKey?: string) { if (![ChainId.Mainnet, ChainId.Rinkeby].includes(chainId)) return - const currentEnv = env ?? NonFungibleTokenAPI.APIEnv.browser + try { const response = await fetch(urlcat(OPENSEA_API_URL, url), { method: 'GET', headers: { 'x-api-key': apiKey ?? OPENSEA_API_KEY, Accept: 'application/json' }, - ...(currentEnv === NonFungibleTokenAPI.APIEnv.browser && { mode: 'cors' }), + ...(!isProxyENV && { mode: 'cors' }), }) if (response.status === 404) return return response.json() as Promise @@ -243,10 +244,8 @@ function createAssetOrder(order: OpenSeaAssetOrder): NonFungibleTokenAPI.AssetOr export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { private readonly _apiKey - private readonly _env: NonFungibleTokenAPI.APIEnv - constructor(apiKey?: string, env?: NonFungibleTokenAPI.APIEnv) { + constructor(apiKey?: string) { this._apiKey = apiKey - this._env = env ?? NonFungibleTokenAPI.APIEnv.browser } async getAsset(address: string, tokenId: string, { chainId = ChainId.Mainnet }: { chainId?: ChainId } = {}) { const requestPath = urlcat('/api/v1/asset/:address/:tokenId', { address, tokenId }) @@ -277,12 +276,7 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { limit: size, collection: opts.pageInfo?.collection, }) - const response = await fetchFromOpenSea<{ assets: OpenSeaResponse[] }>( - requestPath, - chainId, - this._apiKey, - this._env, - ) + const response = await fetchFromOpenSea<{ assets: OpenSeaResponse[] }>(requestPath, chainId, this._apiKey) const assets = response?.assets .filter( @@ -340,7 +334,7 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { offset: page * size, limit: size, }) - const response = await fetchFromOpenSea(requestPath, chainId, this._apiKey, this._env) + const response = await fetchFromOpenSea(requestPath, chainId, this._apiKey) if (!response) { return { data: [], @@ -353,7 +347,12 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { name: x.name, image: x.image_url || undefined, slug: x.slug, - address: x.address, + id: x.slug, + chainId, + symbol: x.primary_asset_contracts?.[0]?.symbol, + address: x.primary_asset_contracts?.[0]?.address, + iconURL: x.image_url, + balance: x.owned_asset_count, })) ?? [] return { @@ -364,11 +363,11 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { } export function getOpenSeaNFTList(apiKey: string, address: string, page?: number, size?: number) { - const opensea = new OpenSeaAPI(apiKey, NonFungibleTokenAPI.APIEnv.proxy) + const opensea = new OpenSeaAPI(apiKey) return opensea.getTokens(address, { page, size }) } export function getOpenSeaCollectionList(apiKey: string, address: string, page?: number, size?: number) { - const opensea = new OpenSeaAPI(apiKey, NonFungibleTokenAPI.APIEnv.proxy) + const opensea = new OpenSeaAPI(apiKey) return opensea.getCollections(address, { page, size }) } diff --git a/packages/web3-providers/src/opensea/types.ts b/packages/web3-providers/src/opensea/types.ts index 67548e1c167e..9ee7062c7b14 100644 --- a/packages/web3-providers/src/opensea/types.ts +++ b/packages/web3-providers/src/opensea/types.ts @@ -79,6 +79,12 @@ export interface OpenSeaCollection extends OpenSeaFees { external_link?: string wiki_link?: string safelist_request_status: string + owned_asset_count: number + primary_asset_contracts: { + address: string + asset_contract_type: string + symbol: string + }[] } export interface OpenSeaResponse extends Asset { diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 374c1b4a5824..2c8e53ddb5c6 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -18,6 +18,7 @@ import { import { RaribleUserURL, RaribleRopstenUserURL, RaribleMainnetURL, RaribleChainURL, RaribleURL } from './constants' import { toRaribleImage } from './utils' import { NonFungibleTokenAPI } from '..' +import { isProxyENV } from '../helpers' const resolveRaribleUserNetwork = createLookupTableResolver( { @@ -27,10 +28,9 @@ const resolveRaribleUserNetwork = createLookupTableResolver(url: string, path: string, init?: RequestInit, env?: NonFungibleTokenAPI.APIEnv) { - const currentEnv = env ?? NonFungibleTokenAPI.APIEnv.browser +async function fetchFromRarible(url: string, path: string, init?: RequestInit) { const response = await fetch(urlcat(url, path), { - ...(currentEnv === NonFungibleTokenAPI.APIEnv.browser && { mode: 'cors' }), + ...(!isProxyENV && { mode: 'cors' }), ...init, }) return response.json() as Promise @@ -142,10 +142,6 @@ function _getAsset(address: string, tokenId: string) { } export class RaribleAPI implements NonFungibleTokenAPI.Provider { - private readonly _env: NonFungibleTokenAPI.APIEnv - constructor(env?: NonFungibleTokenAPI.APIEnv) { - this._env = env ?? NonFungibleTokenAPI.APIEnv.browser - } async getAsset(address: string, tokenId: string, { chainId = ChainId.Mainnet }: { chainId?: ChainId } = {}) { const asset = await _getAsset(address, tokenId) if (!asset) return @@ -168,7 +164,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { continuation: string items: RaribleNFTItemMapResponse[] } - const asset = await fetchFromRarible(RaribleURL, requestPath, undefined, this._env) + const asset = await fetchFromRarible(RaribleURL, requestPath, undefined) if (!asset) return { data: [], @@ -341,6 +337,6 @@ export function getRaribleNFTList( size?: number, pageInfo?: { [key in string]: unknown }, ) { - const rarible = new RaribleAPI(NonFungibleTokenAPI.APIEnv.proxy) + const rarible = new RaribleAPI() return rarible.getTokens(address, { page, size, pageInfo }) } diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts index 6a9a77accc55..ff83b7ec3b49 100644 --- a/packages/web3-providers/src/types.ts +++ b/packages/web3-providers/src/types.ts @@ -106,10 +106,6 @@ export namespace PriceAPI { } export namespace NonFungibleTokenAPI { - export enum APIEnv { - browser = 0, - proxy = 1, - } export enum OrderSide { Buy = 0, Sell = 1, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f8f3eb2f180..988aa757a50e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -838,6 +838,7 @@ importers: '@rollup/plugin-commonjs': ^21.0.1 '@rollup/plugin-json': ^4.1.0 '@rollup/plugin-node-resolve': ^13.0.6 + '@rollup/plugin-replace': ^3.0.1 '@rollup/plugin-sucrase': ^4.0.1 date-fns: ^2.27.0 rollup: ^2.60.1 @@ -854,10 +855,11 @@ importers: '@rollup/plugin-commonjs': 21.0.1_rollup@2.62.0 '@rollup/plugin-json': 4.1.0_rollup@2.62.0 '@rollup/plugin-node-resolve': 13.1.1_rollup@2.62.0 + '@rollup/plugin-replace': 3.0.1_rollup@2.62.0 '@rollup/plugin-sucrase': 4.0.1_rollup@2.62.0 date-fns: 2.27.0 rollup: 2.62.0 - rollup-plugin-dts: 4.0.1_f989a9f75f95e6331defeb4529ad92f3 + rollup-plugin-dts: 4.0.1_rollup@2.62.0 packages/public-api: specifiers: {} @@ -5373,6 +5375,16 @@ packages: rollup: 2.62.0 dev: true + /@rollup/plugin-replace/3.0.1_rollup@2.62.0: + resolution: {integrity: sha512-989J5oRzf3mm0pO/0djTijdfEh9U3n63BIXN5X7T4U9BP+fN4oxQ6DvDuBvFaHA6scaHQRclqmKQEkBhB7k7Hg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.62.0 + magic-string: 0.25.7 + rollup: 2.62.0 + dev: true + /@rollup/plugin-sucrase/4.0.1_rollup@2.62.0: resolution: {integrity: sha512-VDVLl+civWOw8eLBa6jOh1SeOBWCUKTyubmi4T+Ab1eFgxUG0OqdNPXVYJbsKAOqfS+Scj5oMHXvzyJmwoJAPQ==} engines: {node: '>=12.0.0'} @@ -21352,7 +21364,7 @@ packages: dependencies: bn.js: 5.2.0 - /rollup-plugin-dts/4.0.1_f989a9f75f95e6331defeb4529ad92f3: + /rollup-plugin-dts/4.0.1_rollup@2.62.0: resolution: {integrity: sha512-DNv5F8pro/r0Hkx3JWKRtJZocDnqXfgypoajeiaNq134rYaFcEIl/oas5PogD1qexMadVijsHyVko1Chig0OOQ==} engines: {node: '>=v12.22.6'} peerDependencies: @@ -21361,7 +21373,6 @@ packages: dependencies: magic-string: 0.25.7 rollup: 2.62.0 - typescript: 4.6.0-dev.20211202 optionalDependencies: '@babel/code-frame': 7.16.0 dev: true