{"version":3,"file":"MultichainTransactionsController.cjs","sourceRoot":"","sources":["../src/MultichainTransactionsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n  AccountsControllerAccountAddedEvent,\n  AccountsControllerAccountRemovedEvent,\n  AccountsControllerListMultichainAccountsAction,\n} from '@metamask/accounts-controller';\nimport {\n  BaseController,\n  type ControllerGetStateAction,\n  type ControllerStateChangeEvent,\n  type RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { isEvmAccountType, type Transaction } from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { KeyringClient } from '@metamask/keyring-snap-client';\nimport type { HandleSnapRequest } from '@metamask/snaps-controllers';\nimport type { SnapId } from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\nimport type { Draft } from 'immer';\n\nimport { MultichainNetwork, TRANSACTIONS_CHECK_INTERVALS } from './constants';\nimport { MultichainTransactionsTracker } from './MultichainTransactionsTracker';\n\nconst controllerName = 'MultichainTransactionsController';\n\n/**\n * PaginationOptions\n *\n * Represents options for paginating transaction results\n * limit - The maximum number of transactions to return\n * next - The cursor for the next page of transactions, or null if there is no next page\n */\nexport type PaginationOptions = {\n  limit: number;\n  next?: string | null;\n};\n\n/**\n * State used by the {@link MultichainTransactionsController} to cache account transactions.\n */\nexport type MultichainTransactionsControllerState = {\n  nonEvmTransactions: {\n    [accountId: string]: TransactionStateEntry;\n  };\n};\n\n/**\n * Constructs the default {@link MultichainTransactionsController} state.\n *\n * @returns The default {@link MultichainTransactionsController} state.\n */\nexport function getDefaultMultichainTransactionsControllerState(): MultichainTransactionsControllerState {\n  return {\n    nonEvmTransactions: {},\n  };\n}\n\n/**\n * Returns the state of the {@link MultichainTransactionsController}.\n */\nexport type MultichainTransactionsControllerGetStateAction =\n  ControllerGetStateAction<\n    typeof controllerName,\n    MultichainTransactionsControllerState\n  >;\n\n/**\n * Updates the transactions of all supported accounts.\n */\nexport type MultichainTransactionsControllerListTransactionsAction = {\n  type: `${typeof controllerName}:updateTransactions`;\n  handler: MultichainTransactionsController['updateTransactions'];\n};\n\n/**\n * Event emitted when the state of the {@link MultichainTransactionsController} changes.\n */\nexport type MultichainTransactionsControllerStateChange =\n  ControllerStateChangeEvent<\n    typeof controllerName,\n    MultichainTransactionsControllerState\n  >;\n\n/**\n * Actions exposed by the {@link MultichainTransactionsController}.\n */\nexport type MultichainTransactionsControllerActions =\n  | MultichainTransactionsControllerGetStateAction\n  | MultichainTransactionsControllerListTransactionsAction;\n\n/**\n * Events emitted by {@link MultichainTransactionsController}.\n */\nexport type MultichainTransactionsControllerEvents =\n  MultichainTransactionsControllerStateChange;\n\n/**\n * Messenger type for the MultichainTransactionsController.\n */\nexport type MultichainTransactionsControllerMessenger =\n  RestrictedControllerMessenger<\n    typeof controllerName,\n    MultichainTransactionsControllerActions | AllowedActions,\n    MultichainTransactionsControllerEvents | AllowedEvents,\n    AllowedActions['type'],\n    AllowedEvents['type']\n  >;\n\n/**\n * Actions that this controller is allowed to call.\n */\nexport type AllowedActions =\n  | HandleSnapRequest\n  | AccountsControllerListMultichainAccountsAction;\n\n/**\n * Events that this controller is allowed to subscribe.\n */\nexport type AllowedEvents =\n  | AccountsControllerAccountAddedEvent\n  | AccountsControllerAccountRemovedEvent;\n\n/**\n * {@link MultichainTransactionsController}'s metadata.\n *\n * This allows us to choose if fields of the state should be persisted or not\n * using the `persist` flag; and if they can be sent to Sentry or not, using\n * the `anonymous` flag.\n */\nconst multichainTransactionsControllerMetadata = {\n  nonEvmTransactions: {\n    persist: true,\n    anonymous: false,\n  },\n};\n\n/**\n * The state of transactions for a specific account.\n */\nexport type TransactionStateEntry = {\n  transactions: Transaction[];\n  next: string | null;\n  lastUpdated: number;\n};\n\n/**\n * The MultichainTransactionsController is responsible for fetching and caching account\n * transactions for non-EVM accounts.\n */\nexport class MultichainTransactionsController extends BaseController<\n  typeof controllerName,\n  MultichainTransactionsControllerState,\n  MultichainTransactionsControllerMessenger\n> {\n  readonly #tracker: MultichainTransactionsTracker;\n\n  constructor({\n    messenger,\n    state,\n  }: {\n    messenger: MultichainTransactionsControllerMessenger;\n    state?: Partial<MultichainTransactionsControllerState>;\n  }) {\n    super({\n      messenger,\n      name: controllerName,\n      metadata: multichainTransactionsControllerMetadata,\n      state: {\n        ...getDefaultMultichainTransactionsControllerState(),\n        ...state,\n      },\n    });\n\n    this.#tracker = new MultichainTransactionsTracker(\n      async (accountId: string, pagination: PaginationOptions) =>\n        await this.#updateTransactions(accountId, pagination),\n    );\n\n    // Register all non-EVM accounts into the tracker\n    for (const account of this.#listAccounts()) {\n      if (this.#isNonEvmAccount(account)) {\n        this.#tracker.track(account.id, this.#getBlockTimeFor(account));\n      }\n    }\n\n    this.messagingSystem.subscribe(\n      'AccountsController:accountAdded',\n      (account) => this.#handleOnAccountAdded(account),\n    );\n    this.messagingSystem.subscribe(\n      'AccountsController:accountRemoved',\n      (accountId: string) => this.#handleOnAccountRemoved(accountId),\n    );\n  }\n\n  /**\n   * Lists the multichain accounts coming from the `AccountsController`.\n   *\n   * @returns A list of multichain accounts.\n   */\n  #listMultichainAccounts(): InternalAccount[] {\n    return this.messagingSystem.call(\n      'AccountsController:listMultichainAccounts',\n    );\n  }\n\n  /**\n   * Lists the accounts that we should get transactions for.\n   *\n   * @returns A list of accounts that we should get transactions for.\n   */\n  #listAccounts(): InternalAccount[] {\n    const accounts = this.#listMultichainAccounts();\n    return accounts.filter((account) => this.#isNonEvmAccount(account));\n  }\n\n  /**\n   * Updates the transactions for one account.\n   *\n   * @param accountId - The ID of the account to update transactions for.\n   * @param pagination - Options for paginating transaction results.\n   */\n  async #updateTransactions(accountId: string, pagination: PaginationOptions) {\n    const account = this.#listAccounts().find(\n      (accountItem) => accountItem.id === accountId,\n    );\n\n    if (account?.metadata.snap) {\n      const response = await this.#getTransactions(\n        account.id,\n        account.metadata.snap.id,\n        pagination,\n      );\n\n      /**\n       * Filter only Solana transactions to ensure they're mainnet\n       * All other chain transactions are included as-is\n       */\n      const transactions = response.data.filter((tx) => {\n        const chain = tx.chain as MultichainNetwork;\n        if (chain.startsWith(MultichainNetwork.Solana)) {\n          return chain === MultichainNetwork.Solana;\n        }\n        return true;\n      });\n\n      this.update((state: Draft<MultichainTransactionsControllerState>) => {\n        const entry: TransactionStateEntry = {\n          transactions,\n          next: response.next,\n          lastUpdated: Date.now(),\n        };\n\n        Object.assign(state.nonEvmTransactions, { [account.id]: entry });\n      });\n    }\n  }\n\n  /**\n   * Gets transactions for an account.\n   *\n   * @param accountId - The ID of the account to get transactions for.\n   * @param snapId - The ID of the snap that manages the account.\n   * @param pagination - Options for paginating transaction results.\n   * @returns A promise that resolves to the transaction data and pagination info.\n   */\n  async #getTransactions(\n    accountId: string,\n    snapId: string,\n    pagination: PaginationOptions,\n  ): Promise<{\n    data: Transaction[];\n    next: string | null;\n  }> {\n    return await this.#getClient(snapId).listAccountTransactions(\n      accountId,\n      pagination,\n    );\n  }\n\n  /**\n   * Updates transactions for a specific account\n   *\n   * @param accountId - The ID of the account to get transactions for.\n   */\n  async updateTransactionsForAccount(accountId: string) {\n    await this.#tracker.updateTransactionsForAccount(accountId);\n  }\n\n  /**\n   * Updates the transactions of all supported accounts. This method doesn't return\n   * anything, but it updates the state of the controller.\n   */\n  async updateTransactions() {\n    await this.#tracker.updateTransactions();\n  }\n\n  /**\n   * Starts the polling process.\n   */\n  start(): void {\n    this.#tracker.start();\n  }\n\n  /**\n   * Stops the polling process.\n   */\n  stop(): void {\n    this.#tracker.stop();\n  }\n\n  /**\n   * Gets the block time for a given account.\n   *\n   * @param account - The account to get the block time for.\n   * @returns The block time for the account.\n   */\n  #getBlockTimeFor(account: InternalAccount): number {\n    if (account.type in TRANSACTIONS_CHECK_INTERVALS) {\n      return TRANSACTIONS_CHECK_INTERVALS[\n        account.type as keyof typeof TRANSACTIONS_CHECK_INTERVALS\n      ];\n    }\n    throw new Error(\n      `Unsupported account type for transactions tracking: ${account.type}`,\n    );\n  }\n\n  /**\n   * Checks for non-EVM accounts.\n   *\n   * @param account - The new account to be checked.\n   * @returns True if the account is a non-EVM account, false otherwise.\n   */\n  #isNonEvmAccount(account: InternalAccount): boolean {\n    return (\n      !isEvmAccountType(account.type) &&\n      // Non-EVM accounts are backed by a Snap for now\n      account.metadata.snap !== undefined\n    );\n  }\n\n  /**\n   * Handles changes when a new account has been added.\n   *\n   * @param account - The new account being added.\n   */\n  async #handleOnAccountAdded(account: InternalAccount) {\n    if (!this.#isNonEvmAccount(account)) {\n      return;\n    }\n\n    this.#tracker.track(account.id, this.#getBlockTimeFor(account));\n  }\n\n  /**\n   * Handles changes when a new account has been removed.\n   *\n   * @param accountId - The account ID being removed.\n   */\n  async #handleOnAccountRemoved(accountId: string) {\n    if (this.#tracker.isTracked(accountId)) {\n      this.#tracker.untrack(accountId);\n    }\n\n    if (accountId in this.state.nonEvmTransactions) {\n      this.update((state: Draft<MultichainTransactionsControllerState>) => {\n        delete state.nonEvmTransactions[accountId];\n      });\n    }\n  }\n\n  /**\n   * Gets a `KeyringClient` for a Snap.\n   *\n   * @param snapId - ID of the Snap to get the client for.\n   * @returns A `KeyringClient` for the Snap.\n   */\n  #getClient(snapId: string): KeyringClient {\n    return new KeyringClient({\n      send: async (request: JsonRpcRequest) =>\n        (await this.messagingSystem.call('SnapController:handleRequest', {\n          snapId: snapId as SnapId,\n          origin: 'metamask',\n          handler: HandlerType.OnKeyringRequest,\n          request,\n        })) as Promise<Json>,\n    });\n  }\n}\n"]}