{"version":3,"file":"MultichainRoutingService.mjs","sourceRoot":"","sources":["../../src/multichain/MultichainRoutingService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Messenger } from '@metamask/messenger';\nimport type { PermissionControllerGetPermissionsAction } from '@metamask/permission-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport {\n  getProtocolCaveatScopes,\n  SnapEndowments,\n} from '@metamask/snaps-rpc-methods';\nimport type { Json, JsonRpcRequest, SnapId } from '@metamask/snaps-sdk';\nimport type { InternalAccount } from '@metamask/snaps-utils';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport type {\n  CaipAccountId,\n  CaipChainId,\n  JsonRpcParams,\n} from '@metamask/utils';\nimport {\n  assert,\n  hasProperty,\n  isObject,\n  KnownCaipNamespace,\n  parseCaipAccountId,\n} from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport type { MultichainRoutingServiceMethodActions } from './MultichainRoutingService-method-action-types';\nimport type {\n  SnapControllerGetRunnableSnapsAction,\n  SnapControllerHandleRequestAction,\n} from '../snaps';\n\ntype SnapKeyring = {\n  submitRequest: (request: {\n    origin: string;\n    account: string;\n    method: string;\n    params?: Json[] | Record<string, Json>;\n    scope: CaipChainId;\n  }) => Promise<Json>;\n};\n\n// Expecting a bound function that calls KeyringController.withKeyring selecting the Snap keyring\nexport type WithSnapKeyringFunction = <ReturnType>(\n  operation: ({ keyring }: { keyring: SnapKeyring }) => Promise<ReturnType>,\n) => Promise<ReturnType>;\n\nexport type AccountsControllerListMultichainAccountsAction = {\n  type: `AccountsController:listMultichainAccounts`;\n  handler: (chainId?: CaipChainId) => InternalAccount[];\n};\n\nexport type MultichainRoutingServiceActions =\n  MultichainRoutingServiceMethodActions;\n\ntype AllowedActions =\n  | SnapControllerGetRunnableSnapsAction\n  | SnapControllerHandleRequestAction\n  | PermissionControllerGetPermissionsAction\n  | AccountsControllerListMultichainAccountsAction;\n\nexport type MultichainRoutingServiceEvents = never;\n\nexport type MultichainRoutingServiceMessenger = Messenger<\n  typeof name,\n  MultichainRoutingServiceActions | AllowedActions\n>;\n\nexport type MultichainRoutingServiceArgs = {\n  messenger: MultichainRoutingServiceMessenger;\n  withSnapKeyring: WithSnapKeyringFunction;\n};\n\ntype ProtocolSnap = {\n  snapId: SnapId;\n  methods: string[];\n};\n\nconst name = 'MultichainRoutingService';\n\nconst MESSENGER_EXPOSED_METHODS = [\n  'handleRequest',\n  'getSupportedMethods',\n  'getSupportedAccounts',\n  'isSupportedScope',\n] as const;\n\nexport class MultichainRoutingService {\n  name: typeof name = name;\n\n  state = null;\n\n  readonly #messenger: MultichainRoutingServiceMessenger;\n\n  readonly #withSnapKeyring: WithSnapKeyringFunction;\n\n  constructor({ messenger, withSnapKeyring }: MultichainRoutingServiceArgs) {\n    this.#messenger = messenger;\n    this.#withSnapKeyring = withSnapKeyring;\n\n    this.#messenger.registerMethodActionHandlers(\n      this,\n      MESSENGER_EXPOSED_METHODS,\n    );\n  }\n\n  /**\n   * Attempts to resolve the account address to use for a given request by inspecting the request itself.\n   *\n   * The request is sent to an account Snap that will attempt this resolution.\n   *\n   * We manually construct the request instead of using the SnapKeyring, as the keyring may not be available.\n   *\n   * @param snapId - The ID of the Snap to send the request to.\n   * @param scope - The CAIP-2 scope for the request.\n   * @param request - The JSON-RPC request.\n   * @returns The resolved address if found, otherwise null.\n   * @throws If the invocation of the SnapKeyring fails.\n   */\n  async #resolveRequestAddress(\n    snapId: SnapId,\n    scope: CaipChainId,\n    request: JsonRpcRequest,\n  ) {\n    try {\n      const result = await this.#messenger.call(\n        'SnapController:handleRequest',\n        {\n          snapId,\n          origin: 'metamask',\n          request: {\n            method: 'keyring_resolveAccountAddress',\n            params: {\n              request,\n              scope,\n            },\n          },\n          handler: HandlerType.OnKeyringRequest,\n        },\n      );\n\n      assert(result === null || isObject(result));\n\n      const address = result?.address as CaipAccountId;\n      return address ? parseCaipAccountId(address).address : null;\n    } catch {\n      throw rpcErrors.internal();\n    }\n  }\n\n  /**\n   * Get the account ID of the account that should service the RPC request via an account Snap.\n   *\n   * This function checks whether any accounts exist that can service a given request by\n   * using a combination of the resolveAccountAddress functionality and the connected accounts.\n   *\n   * If an account is expected to service this request but none is found, the function will throw.\n   *\n   * @param connectedAddresses - The CAIP-10 addresses connected to the\n   * requesting origin for the requested scope.\n   * @param scope - The CAIP-2 scope for the request.\n   * @param request - The JSON-RPC request.\n   * @returns An account ID if found, otherwise null.\n   * @throws If no account is found, but the accounts exist that could service the request.\n   */\n  async #getSnapAccountId(\n    connectedAddresses: CaipAccountId[],\n    scope: CaipChainId,\n    request: JsonRpcRequest,\n  ) {\n    const accounts = this.#messenger\n      .call('AccountsController:listMultichainAccounts', scope)\n      .filter(\n        (\n          account: InternalAccount,\n        ): account is InternalAccount & {\n          metadata: Required<InternalAccount['metadata']>;\n        } =>\n          Boolean(account.metadata.snap?.enabled) &&\n          account.methods.includes(request.method),\n      );\n\n    // If no accounts can service the request, return null.\n    if (accounts.length === 0) {\n      return null;\n    }\n\n    const parsedConnectedAddresses = connectedAddresses.map(\n      (connectedAddress) => parseCaipAccountId(connectedAddress).address,\n    );\n\n    const connectedAccounts = accounts.filter((account) =>\n      parsedConnectedAddresses.includes(account.address),\n    );\n\n    if (connectedAccounts.length === 0) {\n      throw rpcErrors.invalidParams({\n        message: 'No available account found for request.',\n      });\n    }\n\n    const resolutionSnapId = connectedAccounts[0].metadata.snap.id;\n\n    // Attempt to resolve the address that should be used for signing.\n    const address = await this.#resolveRequestAddress(\n      resolutionSnapId,\n      scope,\n      request,\n    );\n\n    // If we have a resolved address, try to find the selected account based on that\n    // otherwise, default to one of the connected accounts.\n    // TODO: Eventually let the user choose if we have more than one option for the account.\n    const selectedAccount = address\n      ? connectedAccounts.find((account) => account.address === address)\n      : connectedAccounts[0];\n\n    if (!selectedAccount) {\n      throw rpcErrors.invalidParams({\n        message: 'No available account found for request.',\n      });\n    }\n\n    return selectedAccount.id;\n  }\n\n  /**\n   * Get all protocol Snaps that can service a given CAIP-2 scope.\n   *\n   * Protocol Snaps are deemed fit to service a scope if they are runnable\n   * and have the proper permissions set for the scope.\n   *\n   * @param scope - A CAIP-2 scope.\n   * @returns A list of all the protocol Snaps available and their RPC methods.\n   */\n  #getProtocolSnaps(scope: CaipChainId) {\n    const filteredSnaps = this.#messenger.call(\n      'SnapController:getRunnableSnaps',\n    );\n\n    return filteredSnaps.reduce<ProtocolSnap[]>((accumulator, snap) => {\n      const permissions = this.#messenger.call(\n        'PermissionController:getPermissions',\n        snap.id,\n      );\n\n      if (permissions && hasProperty(permissions, SnapEndowments.Protocol)) {\n        const permission = permissions[SnapEndowments.Protocol];\n        const scopes = getProtocolCaveatScopes(permission);\n        if (scopes && hasProperty(scopes, scope)) {\n          accumulator.push({\n            snapId: snap.id,\n            methods: scopes[scope].methods,\n          });\n        }\n      }\n\n      return accumulator;\n    }, []);\n  }\n\n  /**\n   * Handle an incoming JSON-RPC request tied to a specific scope by routing\n   * to either a protocol Snap or an account Snap.\n   *\n   * Note: Addresses are considered case-sensitive by the MultichainRoutingService as\n   * not all non-EVM chains are case-insensitive.\n   *\n   * @param options - An options bag.\n   * @param options.connectedAddresses - Addresses currently connected to the\n   * origin for the requested scope.\n   * @param options.origin - The origin of the RPC request.\n   * @param options.request - The JSON-RPC request.\n   * @param options.scope - The CAIP-2 scope for the request.\n   * @returns The response from the chosen Snap.\n   * @throws If no handler was found.\n   */\n  async handleRequest({\n    connectedAddresses,\n    origin,\n    scope,\n    request: rawRequest,\n  }: {\n    connectedAddresses: CaipAccountId[];\n    origin: string;\n    scope: CaipChainId;\n    request: JsonRpcRequest;\n  }): Promise<Json> {\n    // Explicitly block EVM scopes, just in case.\n    assert(\n      !scope.startsWith(KnownCaipNamespace.Eip155) &&\n        !scope.startsWith('wallet:eip155'),\n    );\n\n    // Re-create the request to simplify and remove additional properties that may be present in MM middleware.\n    const request = {\n      jsonrpc: '2.0' as const,\n      id: rawRequest.id ?? nanoid(),\n      method: rawRequest.method,\n      ...(rawRequest.params ? { params: rawRequest.params } : {}),\n    };\n\n    const { method, params } = request;\n\n    // If the RPC request can be serviced by an account Snap, route it there.\n    const accountId = await this.#getSnapAccountId(\n      connectedAddresses,\n      scope,\n      request,\n    );\n\n    if (accountId) {\n      return this.#withSnapKeyring(async ({ keyring }) =>\n        keyring.submitRequest({\n          origin,\n          account: accountId,\n          scope,\n          method,\n          params: params as JsonRpcParams,\n        }),\n      );\n    }\n\n    // If the RPC request cannot be serviced by an account Snap,\n    // but has a protocol Snap available, route it there.\n    const protocolSnaps = this.#getProtocolSnaps(scope);\n    const protocolSnap = protocolSnaps.find((snap) =>\n      snap.methods.includes(method),\n    );\n\n    if (protocolSnap) {\n      return this.#messenger.call('SnapController:handleRequest', {\n        snapId: protocolSnap.snapId,\n        origin,\n        request: {\n          method: '',\n          params: {\n            request,\n            scope,\n          },\n        },\n        handler: HandlerType.OnProtocolRequest,\n      }) as Promise<Json>;\n    }\n\n    // If no compatible account or protocol Snaps were found, throw.\n    throw rpcErrors.methodNotFound();\n  }\n\n  /**\n   * Get a list of metadata for supported accounts for a given scope from the client.\n   *\n   * @param scope - The CAIP-2 scope.\n   * @returns A list of metadata for the supported accounts.\n   */\n  #getSupportedAccountsMetadata(scope: CaipChainId): InternalAccount[] {\n    return this.#messenger\n      .call('AccountsController:listMultichainAccounts', scope)\n      .filter((account: InternalAccount) => account.metadata.snap?.enabled);\n  }\n\n  /**\n   * Get a list of supported methods for a given scope.\n   * This combines both protocol and account Snaps supported methods.\n   *\n   * @param scope - The CAIP-2 scope.\n   * @returns A list of supported methods.\n   */\n  getSupportedMethods(scope: CaipChainId): string[] {\n    const accountMethods = this.#getSupportedAccountsMetadata(scope).flatMap(\n      (account) => account.methods,\n    );\n\n    const protocolMethods = this.#getProtocolSnaps(scope).flatMap(\n      (snap) => snap.methods,\n    );\n\n    return Array.from(new Set([...accountMethods, ...protocolMethods]));\n  }\n\n  /**\n   * Get a list of supported accounts for a given scope.\n   *\n   * @param scope - The CAIP-2 scope.\n   * @returns A list of CAIP-10 addresses.\n   */\n  getSupportedAccounts(scope: CaipChainId): string[] {\n    return this.#getSupportedAccountsMetadata(scope).map(\n      (account) => `${scope}:${account.address}`,\n    );\n  }\n\n  /**\n   * Determine whether a given CAIP-2 scope is supported by the router.\n   *\n   * @param scope - The CAIP-2 scope.\n   * @returns True if the router can service the scope, otherwise false.\n   */\n  isSupportedScope(scope: CaipChainId): boolean {\n    const hasAccountSnap = this.#messenger\n      .call('AccountsController:listMultichainAccounts', scope)\n      .some((account: InternalAccount) => account.metadata.snap?.enabled);\n    // We currently assume here that if one Snap exists that service the scope, we can service the scope generally.\n    return hasAccountSnap || this.#getProtocolSnaps(scope).length > 0;\n  }\n}\n"]}