diff --git a/src/sdk/account/utils/Constants.ts b/src/sdk/account/utils/Constants.ts index 724f4240..6f34a981 100644 --- a/src/sdk/account/utils/Constants.ts +++ b/src/sdk/account/utils/Constants.ts @@ -107,6 +107,20 @@ export const MOCK_MULTI_MODULE_ADDRESS = export const MODULE_TYPE_MULTI = 0 export const NEXUS_DOMAIN_NAME = "Nexus" + +/** + * Legacy fallback for the Nexus EIP-712 domain version. Only correct for MEE + * versions backed by Nexus 1.2.0 (e.g. V2_0_0, V2_1_0). For MEE ≥ V2_2_0 the + * domain version moved to the 1.3.x line and this constant produces signatures + * that the on-chain Nexus contract rejects. + * + * Prefer `getNexusDomainVersion(meeVersion)` from `../../constants` whenever + * the MEE version is known — or `getEip712Domain()` to query the deployed + * contract directly. This constant is kept only as a back-compat default and + * may be removed in a future major release. + * + * @deprecated Use `getNexusDomainVersion(meeVersionOrConfig)` instead. + */ export const NEXUS_DOMAIN_VERSION = "1.2.0" export const NEXUS_DOMAIN_TYPEHASH = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 317d4894..7241a832 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -38,7 +38,8 @@ import { NEXUS_DOMAIN_TYPEHASH, NEXUS_DOMAIN_VERSION } from "../../account/utils/Constants" -import { MEEVersion } from "../../constants" +import { MEEVersion, getNexusDomainVersion } from "../../constants" +import type { MEEVersionConfig } from "./getVersion" import { EIP1271Abi } from "../../constants/abi" import { type AnyData, @@ -286,8 +287,12 @@ export function convertToFactor(percentage: number | undefined): number { * * @param accountOwner - The account owner address * @param modules - Array of modules with their types and configurations - * @param domainName - Optional domain name - * @param domainVersion - Optional domain version + * @param domainName - Optional domain name. Defaults to `NEXUS_DOMAIN_NAME`. + * @param domainVersion - Optional domain version. **Defaults to the legacy + * `NEXUS_DOMAIN_VERSION` ("1.2.0"), which is wrong for any MEE ≥ V2_2_0.** + * Callers should pass `getNexusDomainVersion(meeVersionOrConfig)` whenever + * the MEE version is known so the install-mode signature is verifiable on + * the Nexus 1.3.x line. * @returns Tuple of [installData, hash] */ export function makeInstallDataAndHash( @@ -382,13 +387,26 @@ export function getTypesForEIP712Domain({ /** * Retrieves account metadata including name, version, and chain ID. * + * Tries `eip712Domain()` on-chain first. If the SCA is not yet deployed + * (counterfactual) or the call fails for any reason, falls back to the static + * domain name + version. When `meeVersionOrConfig` is supplied, the fallback + * version is derived from that config's `accountId` (e.g. MEE v2.2.2 → + * `"1.3.2"`); otherwise it falls back to the legacy `NEXUS_DOMAIN_VERSION` + * constant, which only reflects Nexus 1.2.0 and is wrong for any MEE ≥ 2.2.x. + * Pass `meeVersionOrConfig` whenever you know which MEE version the account + * is being used with. + * * @param client - The viem Client instance * @param accountAddress - The account address to query + * @param meeVersionOrConfig - Optional MEE version (or resolved config) used + * to derive the correct fallback domain version. Strongly recommended when + * the account may not yet be deployed. * @returns Promise resolving to account metadata */ export const getAccountMeta = async ( client: Client, - accountAddress: Address + accountAddress: Address, + meeVersionOrConfig?: MEEVersion | MEEVersionConfig ): Promise => { try { const domain = await client.request({ @@ -420,7 +438,9 @@ export const getAccountMeta = async ( } catch (error) {} return { name: NEXUS_DOMAIN_NAME, - version: NEXUS_DOMAIN_VERSION, + version: meeVersionOrConfig + ? getNexusDomainVersion(meeVersionOrConfig) + : NEXUS_DOMAIN_VERSION, chainId: client.chain ? BigInt(client.chain.id) : BigInt(await client.extend(publicActions).getChainId()) diff --git a/src/sdk/constants/index.ts b/src/sdk/constants/index.ts index f830686b..7ad8074f 100644 --- a/src/sdk/constants/index.ts +++ b/src/sdk/constants/index.ts @@ -187,6 +187,44 @@ export const DEFAULT_CONFIGURATIONS_BY_MEE_VERSION: Record< } } +/** + * The Nexus EIP-712 domain version string for a given MEE version (or its + * already-resolved config). + * + * Each `MEEVersionConfig.accountId` is formatted as `biconomy.nexus.` — + * the same string the on-chain Nexus contract returns from + * `_domainNameAndVersion()`. This helper strips the prefix and returns the + * version suffix, giving callers a reliable fallback when an on-chain + * `eip712Domain()` query is unavailable (e.g. for a counterfactual SCA whose + * factory hasn't been deployed yet). + * + * @example + * getNexusDomainVersion(MEEVersion.V2_2_2) // → "1.3.2" + * getNexusDomainVersion(getMEEVersion(MEEVersion.V2_1_0)) // → "1.2.0" + */ +export const getNexusDomainVersion = ( + meeVersionOrConfig: MEEVersion | MEEVersionConfig +): string => { + const config = + typeof meeVersionOrConfig === "string" + ? DEFAULT_CONFIGURATIONS_BY_MEE_VERSION[meeVersionOrConfig] + : meeVersionOrConfig + if (!config) { + throw new Error( + `Unsupported MEE version: ${String(meeVersionOrConfig)}. ` + + `Expected one of: ${Object.keys(DEFAULT_CONFIGURATIONS_BY_MEE_VERSION).join(", ")}` + ) + } + const prefix = "biconomy.nexus." + if (!config.accountId.startsWith(prefix)) { + throw new Error( + `Unexpected accountId format: "${config.accountId}". ` + + `Expected "${prefix}".` + ) + } + return config.accountId.slice(prefix.length) +} + // Rhinestone constants export { SMART_SESSIONS_ADDRESS,