{"version":3,"file":"NftDetectionController.cjs","sourceRoot":"","sources":["../src/NftDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type { ApprovalControllerAddRequestAction } from '@metamask/approval-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n  ControllerGetStateAction,\n  ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport {\n  toChecksumHexAddress,\n  ChainId,\n  NFT_API_BASE_URL,\n  NFT_API_VERSION,\n  convertHexToDecimal,\n  handleFetch,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n  NetworkClient,\n  NetworkControllerFindNetworkClientIdByChainIdAction,\n  NetworkControllerGetNetworkClientByIdAction,\n  NetworkControllerGetStateAction,\n  NetworkControllerStateChangeEvent,\n} from '@metamask/network-controller';\nimport type {\n  PreferencesControllerGetStateAction,\n  PreferencesControllerStateChangeEvent,\n  PreferencesState,\n} from '@metamask/preferences-controller';\nimport { createDeferredPromise } from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\n\nimport { Source } from './constants';\nimport type {\n  NftController,\n  NftControllerState,\n  NftMetadata,\n} from './NftController';\n\nconst controllerName = 'NftDetectionController';\n\nexport type NFTDetectionControllerState = Record<never, never>;\n\nexport type AllowedActions =\n  | ControllerGetStateAction<typeof controllerName, NFTDetectionControllerState>\n  | ApprovalControllerAddRequestAction\n  | NetworkControllerGetStateAction\n  | NetworkControllerGetNetworkClientByIdAction\n  | PreferencesControllerGetStateAction\n  | AccountsControllerGetSelectedAccountAction\n  | NetworkControllerFindNetworkClientIdByChainIdAction;\n\nexport type AllowedEvents =\n  | ControllerStateChangeEvent<\n      typeof controllerName,\n      NFTDetectionControllerState\n    >\n  | PreferencesControllerStateChangeEvent\n  | NetworkControllerStateChangeEvent;\n\nexport type NftDetectionControllerMessenger = Messenger<\n  typeof controllerName,\n  AllowedActions,\n  AllowedEvents\n>;\n\n/**\n * @type ApiNft\n *\n * NFT object coming from OpenSea api\n *\n * @property token_id - The NFT identifier\n * @property num_sales - Number of sales\n * @property background_color - The background color to be displayed with the item\n * @property image_url - URI of an image associated with this NFT\n *\n * @property image_preview_url - URI of a smaller image associated with this NFT\n * @property image_thumbnail_url - URI of a thumbnail image associated with this NFT\n * @property image_original_url - URI of the original image associated with this NFT\n * @property animation_url - URI of a animation associated with this NFT\n * @property animation_original_url - URI of the original animation associated with this NFT\n * @property name - The NFT name\n * @property description - The NFT description\n * @property external_link - External link containing additional information\n * @property assetContract - The NFT contract information object\n * @property creator - The NFT owner information object\n * @property lastSale - When this item was last sold\n */\n/* eslint-disable @typescript-eslint/naming-convention */\nexport type ApiNft = {\n  token_id: string;\n  num_sales: number | null;\n  background_color: string | null;\n  image_url: string | null;\n  image_preview_url: string | null;\n  image_thumbnail_url: string | null;\n  image_original_url: string | null;\n  animation_url: string | null;\n  animation_original_url: string | null;\n  name: string | null;\n  description: string | null;\n  external_link: string | null;\n  asset_contract: ApiNftContract;\n  creator: ApiNftCreator;\n  last_sale: ApiNftLastSale | null;\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\n/**\n * @type ApiNftContract\n *\n * NFT contract object coming from OpenSea api\n *\n * @property address - Address of the NFT contract\n * @property asset_contract_type - The NFT type, it could be `semi-fungible` or `non-fungible`\n * @property created_date - Creation date\n * @property collection - Object containing the contract name and URI of an image associated\n * @property schema_name - The schema followed by the contract, it could be `ERC721` or `ERC1155`\n * @property symbol - The NFT contract symbol\n * @property total_supply - Total supply of NFTs\n * @property description - The NFT contract description\n * @property external_link - External link containing additional information\n */\n/* eslint-disable @typescript-eslint/naming-convention */\nexport type ApiNftContract = {\n  address: string;\n  asset_contract_type: string | null;\n  created_date: string | null;\n  schema_name: string | null;\n  symbol: string | null;\n  total_supply: string | null;\n  description: string | null;\n  external_link: string | null;\n  collection: {\n    name: string | null;\n    image_url?: string | null;\n    tokenCount?: string | null;\n  };\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\n/**\n * @type ApiNftLastSale\n *\n * NFT sale object coming from OpenSea api\n *\n * @property event_timestamp - Object containing a `username`\n * @property total_price - URI of NFT image associated with this owner\n * @property transaction - Object containing transaction_hash and block_hash\n */\n/* eslint-disable @typescript-eslint/naming-convention */\nexport type ApiNftLastSale = {\n  event_timestamp: string;\n  total_price: string;\n  transaction: { transaction_hash: string; block_hash: string };\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\n/**\n * @type ApiNftCreator\n *\n * NFT creator object coming from OpenSea api\n *\n * @property user - Object containing a `username`\n * @property profile_img_url - URI of NFT image associated with this owner\n * @property address - The owner address\n */\n/* eslint-disable @typescript-eslint/naming-convention */\nexport type ApiNftCreator = {\n  user: { username: string };\n  profile_img_url: string;\n  address: string;\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\nexport type ReservoirResponse = {\n  tokens: TokensResponse[];\n  continuation?: string | null;\n};\n\nexport type TokensResponse = {\n  token: TokenResponse;\n  ownership: Ownership;\n  market?: Market;\n  blockaidResult?: Blockaid;\n};\n\nexport enum BlockaidResultType {\n  Benign = 'Benign',\n  Spam = 'Spam',\n  Warning = 'Warning',\n  Malicious = 'Malicious',\n}\n\n/* eslint-disable @typescript-eslint/naming-convention */\nexport type Blockaid = {\n  contract: string;\n  chainId: number;\n  result_type: BlockaidResultType;\n  malicious_score: string;\n  attack_types: object;\n};\n/* eslint-enable @typescript-eslint/naming-convention */\n\nexport type Market = {\n  floorAsk?: FloorAsk;\n  topBid?: TopBid;\n};\n\nexport type TokenResponse = {\n  chainId: number;\n  contract: string;\n  tokenId: string;\n  kind?: string;\n  name?: string;\n  image?: string;\n  imageSmall?: string;\n  imageLarge?: string;\n  metadata?: Metadata;\n  description?: string;\n  supply?: number;\n  remainingSupply?: number;\n  rarityScore?: number;\n  rarity?: number;\n  rarityRank?: number;\n  media?: string;\n  isFlagged?: boolean;\n  isSpam?: boolean;\n  isNsfw?: boolean;\n  metadataDisabled?: boolean;\n  lastFlagUpdate?: string;\n  lastFlagChange?: string;\n  collection?: Collection;\n  lastSale?: LastSale;\n  topBid?: TopBid;\n  lastAppraisalValue?: number;\n  attributes?: Attributes[];\n};\n\nexport type TopBid = {\n  id?: string;\n  price?: Price;\n  source?: {\n    id?: string;\n    domain?: string;\n    name?: string;\n    icon?: string;\n    url?: string;\n  };\n};\n\nexport type LastSale = {\n  saleId?: string;\n  token?: {\n    contract?: string;\n    tokenId?: string;\n    name?: string;\n    image?: string;\n    collection?: {\n      id?: string;\n      name?: string;\n    };\n  };\n  orderSource?: string;\n  orderSide?: 'ask' | 'bid';\n  orderKind?: string;\n  orderId?: string;\n  from?: string;\n  to?: string;\n  amount?: string;\n  fillSource?: string;\n  block?: number;\n  txHash?: string;\n  logIndex?: number;\n  batchIndex?: number;\n  timestamp?: number;\n  price?: Price;\n  washTradingScore?: number;\n  royaltyFeeBps?: number;\n  marketplaceFeeBps?: number;\n  paidFullRoyalty?: boolean;\n  feeBreakdown?: FeeBreakdown[];\n  isDeleted?: boolean;\n  createdAt?: string;\n  updatedAt?: string;\n};\n\nexport type FeeBreakdown = {\n  kind?: string;\n  bps?: number;\n  recipient?: string;\n  source?: string;\n  rawAmount?: string;\n};\n\nexport type Attributes = {\n  key?: string;\n  kind?: string;\n  value: string;\n  tokenCount?: number;\n  onSaleCount?: number;\n  floorAskPrice?: Price | null;\n  topBidValue?: number | null;\n  createdAt?: string;\n};\n\nexport type GetCollectionsResponse = {\n  collections: CollectionResponse[];\n};\n\nexport type CollectionResponse = {\n  id?: string;\n  chainId?: number;\n  openseaVerificationStatus?: string;\n  contractDeployedAt?: string;\n  creator?: string;\n  ownerCount?: string;\n  topBid?: TopBid & {\n    sourceDomain?: string;\n  };\n};\n\nexport type FloorAskCollection = {\n  id?: string;\n  price?: Price;\n  maker?: string;\n  kind?: string;\n  validFrom?: number;\n  validUntil?: number;\n  source?: SourceCollection;\n  rawData?: Metadata;\n  isNativeOffChainCancellable?: boolean;\n};\n\nexport type SourceCollection = {\n  id: string;\n  domain: string;\n  name: string;\n  icon: string;\n  url: string;\n};\n\nexport type TokenCollection = {\n  id?: string;\n  name?: string;\n  slug?: string;\n  symbol?: string;\n  imageUrl?: string;\n  image?: string;\n  isSpam?: boolean;\n  isNsfw?: boolean;\n  creator?: string;\n  tokenCount?: string;\n  metadataDisabled?: boolean;\n  openseaVerificationStatus?: string;\n  floorAskPrice?: Price;\n  royaltiesBps?: number;\n  royalties?: Royalties[];\n  floorAsk?: FloorAskCollection;\n};\n\nexport type Collection = TokenCollection & CollectionResponse;\n\nexport type Royalties = {\n  bps?: number;\n  recipient?: string;\n};\n\nexport type Ownership = {\n  tokenCount?: string;\n  onSaleCount?: string;\n  floorAsk?: FloorAsk;\n  acquiredAt?: string;\n};\n\nexport type FloorAsk = {\n  id?: string;\n  price?: Price;\n  maker?: string;\n  kind?: string;\n  validFrom?: number;\n  validUntil?: number;\n  source?: Source;\n  rawData?: Metadata;\n  isNativeOffChainCancellable?: boolean;\n};\n\nexport type Price = {\n  currency?: {\n    contract?: string;\n    name?: string;\n    symbol?: string;\n    decimals?: number;\n    chainId?: number;\n  };\n  amount?: {\n    raw?: string;\n    decimal?: number;\n    usd?: number;\n    native?: number;\n  };\n  netAmount?: {\n    raw?: string;\n    decimal?: number;\n    usd?: number;\n    native?: number;\n  };\n};\n\nexport type Metadata = {\n  imageOriginal?: string;\n  tokenURI?: string;\n};\n\n/**\n * Controller that passively detects nfts for a user address\n */\nexport class NftDetectionController extends BaseController<\n  typeof controllerName,\n  NFTDetectionControllerState,\n  NftDetectionControllerMessenger\n> {\n  #disabled: boolean;\n\n  readonly #addNfts: NftController['addNfts'];\n\n  readonly #getNftState: () => NftControllerState;\n\n  #inProcessNftFetchingUpdates: Record<`${string}:${string}`, Promise<void>>;\n\n  /**\n   * The controller options\n   *\n   * @param options - The controller options.\n   * @param options.messenger - A reference to the messaging system.\n   * @param options.disabled - Represents previous value of useNftDetection. Used to detect changes of useNftDetection. Default value is true.\n   * @param options.addNfts - Add multiple NFTs.\n   * @param options.getNftState - Gets the current state of the Assets controller.\n   */\n  constructor({\n    messenger,\n    disabled = false,\n    addNfts,\n    getNftState,\n  }: {\n    messenger: NftDetectionControllerMessenger;\n    disabled: boolean;\n    addNfts: NftController['addNfts'];\n    getNftState: () => NftControllerState;\n  }) {\n    super({\n      name: controllerName,\n      messenger,\n      metadata: {},\n      state: {},\n    });\n    this.#disabled = disabled;\n    this.#inProcessNftFetchingUpdates = {};\n\n    this.#getNftState = getNftState;\n    this.#addNfts = addNfts;\n\n    this.messenger.subscribe(\n      'PreferencesController:stateChange',\n      this.#onPreferencesControllerStateChange.bind(this),\n    );\n  }\n\n  /**\n   * Checks whether network is mainnet or not.\n   *\n   * @returns Whether current network is mainnet.\n   */\n  isMainnet(): boolean {\n    const { selectedNetworkClientId } = this.messenger.call(\n      'NetworkController:getState',\n    );\n    const {\n      configuration: { chainId },\n    } = this.messenger.call(\n      'NetworkController:getNetworkClientById',\n      selectedNetworkClientId,\n    );\n    return chainId === ChainId.mainnet;\n  }\n\n  isMainnetByNetworkClientId(networkClient: NetworkClient): boolean {\n    return networkClient.configuration.chainId === ChainId.mainnet;\n  }\n\n  /**\n   * Handles the state change of the preference controller.\n   *\n   * @param preferencesState - The new state of the preference controller.\n   * @param preferencesState.useNftDetection - Boolean indicating user preference on NFT detection.\n   */\n  #onPreferencesControllerStateChange({\n    useNftDetection,\n  }: PreferencesState): void {\n    if (!useNftDetection !== this.#disabled) {\n      this.#disabled = !useNftDetection;\n    }\n  }\n\n  #getOwnerNftApi({\n    chainIds,\n    address,\n    next,\n  }: {\n    chainIds: string[];\n    address: string;\n    next?: string;\n  }): string {\n    // from chainIds construct a string of chainIds that can be used like chainIds=1&chainIds=56\n    const chainIdsString = chainIds.join('&chainIds=');\n    return `${\n      NFT_API_BASE_URL as string\n    }/users/${address}/tokens?chainIds=${chainIdsString}&limit=50&includeTopBid=true&continuation=${next ?? ''}`;\n  }\n\n  async #getOwnerNfts(\n    address: string,\n    chainIds: Hex[],\n    cursor: string | undefined,\n  ): Promise<ReservoirResponse> {\n    // Convert hex chainId to number\n    const convertedChainIds = chainIds.map((chainId) =>\n      convertHexToDecimal(chainId).toString(),\n    );\n\n    const filteredChainIds = convertedChainIds.filter(\n      (chainId) => chainId !== '0',\n    );\n\n    // Avoid making the API call for non-EVM chains\n    if (filteredChainIds.length === 0) {\n      return {\n        tokens: [],\n        continuation: null,\n      };\n    }\n\n    const url = this.#getOwnerNftApi({\n      chainIds: filteredChainIds,\n      address,\n      next: cursor,\n    });\n    const nftApiResponse: ReservoirResponse = await handleFetch(url, {\n      headers: {\n        Version: NFT_API_VERSION,\n      },\n    });\n    return nftApiResponse;\n  }\n\n  /**\n   * Triggers asset ERC721 token auto detection on mainnet. Any newly detected NFTs are\n   * added.\n   *\n   * @param chainIds - The chain IDs to detect NFTs on.\n   * @param options - Options bag.\n   * @param options.userAddress - The address to detect NFTs for.\n   * @param options.firstPageOnly - Whether to only detect the first page of NFTs.\n   * @param options.signal - An optional abort signal to cancel the operation.\n   */\n  async detectNfts(\n    chainIds: Hex[],\n    options?: {\n      userAddress?: string;\n      firstPageOnly?: boolean;\n      signal?: AbortSignal;\n    },\n  ): Promise<void> {\n    const userAddress =\n      options?.userAddress ??\n      this.messenger.call('AccountsController:getSelectedAccount').address;\n\n    /* istanbul ignore if */\n    if (chainIds.length === 0 || this.#disabled) {\n      return;\n    }\n    /* istanbul ignore else */\n    if (!userAddress) {\n      return;\n    }\n    // create a string of all chainIds\n    const chainIdsString = chainIds.join(',');\n\n    const updateKey: `${string}:${string}` = `${chainIdsString}:${userAddress}`;\n    if (updateKey in this.#inProcessNftFetchingUpdates) {\n      // This prevents redundant updates\n      // This promise is resolved after the in-progress update has finished,\n      // and state has been updated.\n      await this.#inProcessNftFetchingUpdates[updateKey];\n      return;\n    }\n\n    const {\n      promise: inProgressUpdate,\n      resolve: updateSucceeded,\n      reject: updateFailed,\n    } = createDeferredPromise({ suppressUnhandledRejection: true });\n    this.#inProcessNftFetchingUpdates[updateKey] = inProgressUpdate;\n\n    let next;\n    let apiNfts: TokensResponse[] = [];\n    let resultNftApi: ReservoirResponse;\n    try {\n      do {\n        resultNftApi = await this.#getOwnerNfts(userAddress, chainIds, next);\n        apiNfts = resultNftApi.tokens.filter(\n          (elm) =>\n            elm.token.isSpam === false &&\n            (elm.blockaidResult?.result_type\n              ? elm.blockaidResult?.result_type === BlockaidResultType.Benign\n              : true),\n        );\n\n        // Proceed to add NFTs\n        const nftsToAdd = apiNfts\n          .map((nft) => {\n            const {\n              tokenId,\n              contract,\n              kind,\n              image: imageUrl,\n              imageSmall: imageThumbnailUrl,\n              metadata,\n              name,\n              description,\n              attributes,\n              topBid,\n              lastSale,\n              rarityRank,\n              rarityScore,\n              collection,\n              chainId,\n            } = nft.token;\n\n            // Use a fallback if metadata is null\n            const { imageOriginal: imageOriginalUrl } = metadata ?? {};\n\n            let ignored;\n            /* istanbul ignore else */\n            const { ignoredNfts } = this.#getNftState();\n            if (ignoredNfts.length) {\n              ignored = ignoredNfts.find((ignoredNft) => {\n                /* istanbul ignore next */\n                return (\n                  ignoredNft.address === toChecksumHexAddress(contract) &&\n                  ignoredNft.tokenId === tokenId\n                );\n              });\n            }\n\n            /* istanbul ignore else */\n            if (!ignored) {\n              /* istanbul ignore next */\n              const nftMetadata: NftMetadata & { chainId: number } =\n                Object.assign(\n                  {},\n                  { name },\n                  description && { description },\n                  imageUrl && { image: imageUrl },\n                  imageThumbnailUrl && { imageThumbnail: imageThumbnailUrl },\n                  imageOriginalUrl && { imageOriginal: imageOriginalUrl },\n                  kind && { standard: kind.toUpperCase() },\n                  lastSale && { lastSale },\n                  attributes && { attributes },\n                  topBid && { topBid },\n                  rarityRank && { rarityRank },\n                  rarityScore && { rarityScore },\n                  collection && { collection },\n                  chainId && { chainId },\n                );\n\n              return {\n                tokenAddress: contract,\n                tokenId,\n                nftMetadata,\n              };\n            }\n            return undefined;\n          })\n          .filter((nft): nft is NonNullable<typeof nft> => nft !== undefined);\n\n        await this.#addNfts(nftsToAdd, userAddress, Source.Detected);\n      } while (\n        (next = resultNftApi.continuation) &&\n        !options?.firstPageOnly &&\n        !options?.signal?.aborted\n      );\n      updateSucceeded();\n    } catch (error) {\n      updateFailed(error);\n      throw error;\n    } finally {\n      delete this.#inProcessNftFetchingUpdates[updateKey];\n    }\n  }\n}\n\nexport default NftDetectionController;\n"]}