{"version":3,"file":"TokenSearchDiscoveryDataController.cjs","sourceRoot":"","sources":["../../src/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type {\n  StateMetadata,\n  ControllerGetStateAction,\n  ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type { Hex } from '@metamask/utils';\n\nimport { formatIconUrlWithProxy } from '../assetsUtil';\nimport type { GetCurrencyRateState } from '../CurrencyRateController';\nimport type { AbstractTokenPricesService } from '../token-prices-service';\nimport {\n  fetchTokenMetadata,\n  TOKEN_METADATA_NO_SUPPORT_ERROR,\n} from '../token-service';\nimport type { TokenListToken } from '../TokenListController';\nimport type { TokenDisplayData } from './types';\n\n// === GENERAL ===\n\nexport const controllerName = 'TokenSearchDiscoveryDataController';\n\nexport const MAX_TOKEN_DISPLAY_DATA_LENGTH = 10;\n\n// === STATE ===\n\nexport type TokenSearchDiscoveryDataControllerState = {\n  tokenDisplayData: TokenDisplayData[];\n};\n\nconst tokenSearchDiscoveryDataControllerMetadata: StateMetadata<TokenSearchDiscoveryDataControllerState> =\n  {\n    tokenDisplayData: {\n      includeInStateLogs: false,\n      persist: true,\n      includeInDebugSnapshot: false,\n      usedInUi: true,\n    },\n  } as const;\n\n// === MESSENGER ===\n\n/**\n * The action which can be used to retrieve the state of the\n * {@link TokenSearchDiscoveryDataController}.\n */\nexport type TokenSearchDiscoveryDataControllerGetStateAction =\n  ControllerGetStateAction<\n    typeof controllerName,\n    TokenSearchDiscoveryDataControllerState\n  >;\n\n/**\n * All actions that {@link TokenSearchDiscoveryDataController} registers, to be\n * called externally.\n */\nexport type TokenSearchDiscoveryDataControllerActions =\n  TokenSearchDiscoveryDataControllerGetStateAction;\n\n/**\n * All actions that {@link TokenSearchDiscoveryDataController} calls internally.\n */\nexport type AllowedActions = GetCurrencyRateState;\n\n/**\n * The event that {@link TokenSearchDiscoveryDataController} publishes when updating\n * state.\n */\nexport type TokenSearchDiscoveryDataControllerStateChangeEvent =\n  ControllerStateChangeEvent<\n    typeof controllerName,\n    TokenSearchDiscoveryDataControllerState\n  >;\n\n/**\n * All events that {@link TokenSearchDiscoveryDataController} publishes, to be\n * subscribed to externally.\n */\nexport type TokenSearchDiscoveryDataControllerEvents =\n  TokenSearchDiscoveryDataControllerStateChangeEvent;\n\n/**\n * All events that {@link TokenSearchDiscoveryDataController} subscribes to internally.\n */\nexport type AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link TokenSearchDiscoveryDataController}.\n */\nexport type TokenSearchDiscoveryDataControllerMessenger = Messenger<\n  typeof controllerName,\n  TokenSearchDiscoveryDataControllerActions | AllowedActions,\n  TokenSearchDiscoveryDataControllerEvents | AllowedEvents\n>;\n\n/**\n * Constructs the default {@link TokenSearchDiscoveryDataController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link TokenSearchDiscoveryDataController} state.\n */\nexport function getDefaultTokenSearchDiscoveryDataControllerState(): TokenSearchDiscoveryDataControllerState {\n  return {\n    tokenDisplayData: [],\n  };\n}\n\n/**\n * The TokenSearchDiscoveryDataController manages the retrieval of token search results and token discovery.\n * It fetches token metadata from the Token API and token prices from the token prices service.\n */\nexport class TokenSearchDiscoveryDataController extends BaseController<\n  typeof controllerName,\n  TokenSearchDiscoveryDataControllerState,\n  TokenSearchDiscoveryDataControllerMessenger\n> {\n  readonly #abortController: AbortController;\n\n  readonly #tokenPricesService: AbstractTokenPricesService;\n\n  constructor({\n    state = {},\n    messenger,\n    tokenPricesService,\n  }: {\n    state?: Partial<TokenSearchDiscoveryDataControllerState>;\n    messenger: TokenSearchDiscoveryDataControllerMessenger;\n    tokenPricesService: AbstractTokenPricesService;\n  }) {\n    super({\n      name: controllerName,\n      metadata: tokenSearchDiscoveryDataControllerMetadata,\n      messenger,\n      state: {\n        ...getDefaultTokenSearchDiscoveryDataControllerState(),\n        ...state,\n      },\n    });\n\n    this.#abortController = new AbortController();\n    this.#tokenPricesService = tokenPricesService;\n  }\n\n  async #fetchPriceData(chainId: Hex, address: string) {\n    const { currentCurrency } = this.messenger.call(\n      'CurrencyRateController:getState',\n    );\n\n    try {\n      const pricesData = await this.#tokenPricesService.fetchTokenPrices({\n        assets: [{ chainId, tokenAddress: address as Hex }],\n        currency: currentCurrency,\n      });\n\n      return pricesData[0] ?? null;\n    } catch (error) {\n      console.error(error);\n      return null;\n    }\n  }\n\n  async fetchTokenDisplayData(chainId: Hex, address: string): Promise<void> {\n    let tokenMetadata: TokenListToken | undefined;\n    try {\n      tokenMetadata = await fetchTokenMetadata<TokenListToken>(\n        chainId,\n        address,\n        this.#abortController.signal,\n      );\n    } catch (error) {\n      if (\n        !(error instanceof Error) ||\n        !error.message.includes(TOKEN_METADATA_NO_SUPPORT_ERROR)\n      ) {\n        throw error;\n      }\n    }\n\n    const { currentCurrency } = this.messenger.call(\n      'CurrencyRateController:getState',\n    );\n\n    let tokenDisplayData: TokenDisplayData;\n    if (!tokenMetadata) {\n      tokenDisplayData = {\n        found: false,\n        address,\n        chainId,\n        currency: currentCurrency,\n      };\n    } else {\n      const priceData = await this.#fetchPriceData(chainId, address);\n      tokenDisplayData = {\n        found: true,\n        address,\n        chainId,\n        currency: currentCurrency,\n        token: {\n          ...tokenMetadata,\n          isERC721: false,\n          image: formatIconUrlWithProxy({\n            chainId,\n            tokenAddress: address,\n          }),\n        },\n        price: priceData,\n      };\n    }\n\n    this.update((state) => {\n      state.tokenDisplayData = [\n        tokenDisplayData,\n        ...state.tokenDisplayData.filter(\n          (token) =>\n            token.address !== address ||\n            token.chainId !== chainId ||\n            token.currency !== currentCurrency,\n        ),\n      ].slice(0, MAX_TOKEN_DISPLAY_DATA_LENGTH);\n    });\n  }\n}\n"]}