{"version":3,"file":"DeFiPositionsController.mjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsController.ts"],"names":[],"mappings":";;;;;;;;;;;;AASA,OAAO,EAAE,gBAAgB,EAAE,8BAA8B;AAIzD,OAAO,EAAE,+BAA+B,EAAE,qCAAqC;AAI/E,OAAO,EAAE,4BAA4B,EAAE,qCAAiC;AAExE,OAAO,EAAE,oBAAoB,EAAE,8BAA0B;AACzD,OAAO,EAAE,kBAAkB,EAAE,mCAA+B;AAG5D,MAAM,iBAAiB,GAAG,MAAO,CAAC;AAElC,MAAM,cAAc,GAAG,yBAAyB,CAAC;AAuCjD,MAAM,kBAAkB,GAAgD;IACtE,gBAAgB,EAAE;QAChB,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,KAAK;QACd,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,IAAI;KACf;IACD,qBAAqB,EAAE;QACrB,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,KAAK;QACd,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,sCAAsC,GACjD,GAAiC,EAAE;IACjC,OAAO;QACL,gBAAgB,EAAE,EAAE;QACpB,qBAAqB,EAAE,EAAE;KAC1B,CAAC;AACJ,CAAC,CAAC;AA0CJ;;GAEG;AACH,MAAM,OAAO,uBAAwB,SAAQ,+BAA+B,EAI3E;IASC;;;;;;;OAOG;IACH,YAAY,EACV,SAAS,EACT,SAAS,GAAG,GAAG,EAAE,CAAC,IAAI,EACtB,UAAU,GAKX;QACC,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,kBAAkB;YAC5B,SAAS;YACT,KAAK,EAAE,sCAAsC,EAAE;SAChD,CAAC,CAAC;;QA9BI,0DAE4B;QAE5B,qDAA0B;QAE1B,sDAA6B;QA0BpC,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;QAE1C,uBAAA,IAAI,2CAAmB,oBAAoB,EAAE,MAAA,CAAC;QAC9C,uBAAA,IAAI,sCAAc,SAAS,MAAA,CAAC;QAE5B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,wBAAwB,EAAE,GAAG,EAAE;YACtD,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,4CAA4C,EAC5C,KAAK,EAAE,eAAe,EAAE,EAAE;YACxB,MAAM,eAAe,GAAG,uBAAA,IAAI,yFAAsB,MAA1B,IAAI,CAAwB,CAAC;YAErD,IACE,eAAe,EAAE,WAAW,EAAE;gBAC9B,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,EAC3C,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,uBAAA,IAAI,2FAAwB,MAA5B,IAAI,EAAyB,eAAe,CAAC,CAAC;QACtD,CAAC,CACF,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,kDAAkD,EAClD,KAAK,IAAI,EAAE;YACT,MAAM,eAAe,GAAG,uBAAA,IAAI,yFAAsB,MAA1B,IAAI,CAAwB,CAAC;YAErD,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YAED,MAAM,uBAAA,IAAI,2FAAwB,MAA5B,IAAI,EAAyB,eAAe,CAAC,CAAC;QACtD,CAAC,CACF,CAAC;QAEF,uBAAA,IAAI,uCAAe,UAAU,MAAA,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,uBAAA,IAAI,0CAAW,MAAf,IAAI,CAAa,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,eAAe,GAAG,uBAAA,IAAI,yFAAsB,MAA1B,IAAI,CAAwB,CAAC;QAErD,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,uBAAA,IAAI,0FAAuB,MAA3B,IAAI,EAAwB,eAAe,CAAC,CAAC;QAE5E,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,gBAAgB,CAAC,eAAe,CAAC,GAAG,gBAAgB,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;CAiEF;wQA/DC,KAAK,0DAAyB,cAAsB;IAClD,IAAI,CAAC,uBAAA,IAAI,0CAAW,MAAf,IAAI,CAAa,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,MAAM,wBAAwB,GAC5B,MAAM,uBAAA,IAAI,0FAAuB,MAA3B,IAAI,EAAwB,cAAc,CAAC,CAAC;IAEpD,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACpB,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,GAAG,wBAAwB,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,mDAED,KAAK,yDACH,cAAsB;IAEtB,IAAI,CAAC;QACH,MAAM,qBAAqB,GAAG,MAAM,uBAAA,IAAI,+CAAgB,MAApB,IAAI,EAAiB,cAAc,CAAC,CAAC;QAEzE,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;QAEvE,IAAI,CAAC;YACH,uBAAA,IAAI,gGAA6B,MAAjC,IAAI,EAA8B,oBAAoB,EAAE,cAAc,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,gDAAgD,cAAc,GAAG,EACjE,KAAK,CACN,CAAC;QACJ,CAAC;QAED,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,uHAGC,oBAAkD,EAClD,cAAsB;IAEtB,wDAAwD;IACxD,IAAI,CAAC,uBAAA,IAAI,2CAAY,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,4BAA4B,CAAC,oBAAoB,CAAC,CAAC;IACvE,MAAM,EAAE,cAAc,EAAE,GAAG,WAAW,CAAC,UAAU,CAAC;IAElD,IAAI,cAAc,KAAK,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,CAAC;QACxE,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,CAAC,qBAAqB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,uBAAA,IAAI,2CAAY,EAAE,KAAlB,IAAI,EAAe,WAAW,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;IAGC,OAAO,IAAI,CAAC,SAAS;SAClB,IAAI,CAAC,2DAA2D,CAAC;SACjE,IAAI,CAAC,CAAC,OAAwB,EAAE,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnE,EAAE,OAAO,CAAC;AACd,CAAC","sourcesContent":["import type {\n  AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,\n  AccountTreeControllerSelectedAccountGroupChangeEvent,\n} from '@metamask/account-tree-controller';\nimport type {\n  ControllerGetStateAction,\n  ControllerStateChangeEvent,\n  StateMetadata,\n} from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { KeyringControllerLockEvent } from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { TransactionControllerTransactionConfirmedEvent } from '@metamask/transaction-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { calculateDeFiPositionMetrics } from './calculate-defi-metrics';\nimport type { DefiPositionResponse } from './fetch-positions';\nimport { buildPositionFetcher } from './fetch-positions';\nimport { groupDeFiPositions } from './group-defi-positions';\nimport type { GroupedDeFiPositions } from './group-defi-positions';\n\nconst TEN_MINUTES_IN_MS = 600_000;\n\nconst controllerName = 'DeFiPositionsController';\n\nexport type GroupedDeFiPositionsPerChain = {\n  [chain: Hex]: GroupedDeFiPositions;\n};\n\nexport type TrackingEventPayload = {\n  event: string;\n  category: string;\n  properties: {\n    totalPositions: number;\n    totalMarketValueUSD: number;\n    breakdown?: {\n      protocolId: string;\n      marketValueUSD: number;\n      chainId: Hex;\n      count: number;\n    }[];\n  };\n};\n\ntype TrackEventHook = (event: TrackingEventPayload) => void;\n\nexport type DeFiPositionsControllerState = {\n  /**\n   * Object containing DeFi positions per account and network\n   */\n  allDeFiPositions: {\n    [accountAddress: string]: GroupedDeFiPositionsPerChain | null;\n  };\n\n  /**\n   * Object containing DeFi positions count per account\n   */\n  allDeFiPositionsCount: {\n    [accountAddress: string]: number;\n  };\n};\n\nconst controllerMetadata: StateMetadata<DeFiPositionsControllerState> = {\n  allDeFiPositions: {\n    includeInStateLogs: false,\n    persist: false,\n    includeInDebugSnapshot: false,\n    usedInUi: true,\n  },\n  allDeFiPositionsCount: {\n    includeInStateLogs: false,\n    persist: false,\n    includeInDebugSnapshot: false,\n    usedInUi: false,\n  },\n};\n\nexport const getDefaultDefiPositionsControllerState =\n  (): DeFiPositionsControllerState => {\n    return {\n      allDeFiPositions: {},\n      allDeFiPositionsCount: {},\n    };\n  };\n\nexport type DeFiPositionsControllerActions =\n  DeFiPositionsControllerGetStateAction;\n\nexport type DeFiPositionsControllerGetStateAction = ControllerGetStateAction<\n  typeof controllerName,\n  DeFiPositionsControllerState\n>;\n\nexport type DeFiPositionsControllerEvents =\n  DeFiPositionsControllerStateChangeEvent;\n\nexport type DeFiPositionsControllerStateChangeEvent =\n  ControllerStateChangeEvent<\n    typeof controllerName,\n    DeFiPositionsControllerState\n  >;\n\n/**\n * The external actions available to the {@link DeFiPositionsController}.\n */\nexport type AllowedActions =\n  AccountTreeControllerGetAccountsFromSelectedAccountGroupAction;\n\n/**\n * The external events available to the {@link DeFiPositionsController}.\n */\nexport type AllowedEvents =\n  | KeyringControllerLockEvent\n  | TransactionControllerTransactionConfirmedEvent\n  | AccountTreeControllerSelectedAccountGroupChangeEvent;\n\n/**\n * The messenger of the {@link DeFiPositionsController}.\n */\nexport type DeFiPositionsControllerMessenger = Messenger<\n  typeof controllerName,\n  DeFiPositionsControllerActions | AllowedActions,\n  DeFiPositionsControllerEvents | AllowedEvents\n>;\n\n/**\n * Controller that stores assets and exposes convenience methods\n */\nexport class DeFiPositionsController extends StaticIntervalPollingController()<\n  typeof controllerName,\n  DeFiPositionsControllerState,\n  DeFiPositionsControllerMessenger\n> {\n  readonly #fetchPositions: (\n    accountAddress: string,\n  ) => Promise<DefiPositionResponse[]>;\n\n  readonly #isEnabled: () => boolean;\n\n  readonly #trackEvent?: TrackEventHook;\n\n  /**\n   * DeFiPositionsController constuctor\n   *\n   * @param options - Constructor options.\n   * @param options.messenger - The controller messenger.\n   * @param options.isEnabled - Function that returns whether the controller is enabled. (default: () => true)\n   * @param options.trackEvent - Function to track events. (default: undefined)\n   */\n  constructor({\n    messenger,\n    isEnabled = () => true,\n    trackEvent,\n  }: {\n    messenger: DeFiPositionsControllerMessenger;\n    isEnabled?: () => boolean;\n    trackEvent?: TrackEventHook;\n  }) {\n    super({\n      name: controllerName,\n      metadata: controllerMetadata,\n      messenger,\n      state: getDefaultDefiPositionsControllerState(),\n    });\n\n    this.setIntervalLength(TEN_MINUTES_IN_MS);\n\n    this.#fetchPositions = buildPositionFetcher();\n    this.#isEnabled = isEnabled;\n\n    this.messenger.subscribe('KeyringController:lock', () => {\n      this.stopAllPolling();\n    });\n\n    this.messenger.subscribe(\n      'TransactionController:transactionConfirmed',\n      async (transactionMeta) => {\n        const selectedAddress = this.#getSelectedEvmAdress();\n\n        if (\n          selectedAddress?.toLowerCase() !==\n          transactionMeta.txParams.from.toLowerCase()\n        ) {\n          return;\n        }\n\n        await this.#updateAccountPositions(selectedAddress);\n      },\n    );\n\n    this.messenger.subscribe(\n      'AccountTreeController:selectedAccountGroupChange',\n      async () => {\n        const selectedAddress = this.#getSelectedEvmAdress();\n\n        if (!selectedAddress) {\n          return;\n        }\n\n        await this.#updateAccountPositions(selectedAddress);\n      },\n    );\n\n    this.#trackEvent = trackEvent;\n  }\n\n  async _executePoll(): Promise<void> {\n    if (!this.#isEnabled()) {\n      return;\n    }\n\n    const selectedAddress = this.#getSelectedEvmAdress();\n\n    if (!selectedAddress) {\n      return;\n    }\n\n    const accountPositions = await this.#fetchAccountPositions(selectedAddress);\n\n    this.update((state) => {\n      state.allDeFiPositions[selectedAddress] = accountPositions;\n    });\n  }\n\n  async #updateAccountPositions(accountAddress: string): Promise<void> {\n    if (!this.#isEnabled()) {\n      return;\n    }\n\n    const accountPositionsPerChain =\n      await this.#fetchAccountPositions(accountAddress);\n\n    this.update((state) => {\n      state.allDeFiPositions[accountAddress] = accountPositionsPerChain;\n    });\n  }\n\n  async #fetchAccountPositions(\n    accountAddress: string,\n  ): Promise<GroupedDeFiPositionsPerChain | null> {\n    try {\n      const defiPositionsResponse = await this.#fetchPositions(accountAddress);\n\n      const groupedDeFiPositions = groupDeFiPositions(defiPositionsResponse);\n\n      try {\n        this.#updatePositionsCountMetrics(groupedDeFiPositions, accountAddress);\n      } catch (error) {\n        console.error(\n          `Failed to update positions count for account ${accountAddress}:`,\n          error,\n        );\n      }\n\n      return groupedDeFiPositions;\n    } catch {\n      return null;\n    }\n  }\n\n  #updatePositionsCountMetrics(\n    groupedDeFiPositions: GroupedDeFiPositionsPerChain,\n    accountAddress: string,\n  ) {\n    // If no track event passed then skip the metrics update\n    if (!this.#trackEvent) {\n      return;\n    }\n\n    const defiMetrics = calculateDeFiPositionMetrics(groupedDeFiPositions);\n    const { totalPositions } = defiMetrics.properties;\n\n    if (totalPositions !== this.state.allDeFiPositionsCount[accountAddress]) {\n      this.update((state) => {\n        state.allDeFiPositionsCount[accountAddress] = totalPositions;\n      });\n\n      this.#trackEvent?.(defiMetrics);\n    }\n  }\n\n  #getSelectedEvmAdress(): string | undefined {\n    return this.messenger\n      .call('AccountTreeController:getAccountsFromSelectedAccountGroup')\n      .find((account: InternalAccount) => isEvmAccountType(account.type))\n      ?.address;\n  }\n}\n"]}