diff --git a/.changeset/spicy-boats-taste.md b/.changeset/spicy-boats-taste.md new file mode 100644 index 00000000000..43a2fc2d289 --- /dev/null +++ b/.changeset/spicy-boats-taste.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/wallets": patch +--- + +Patched smart wallet signatures for updated eip-1271 sig verification diff --git a/packages/sdk/src/evm/index.ts b/packages/sdk/src/evm/index.ts index 15bba0f65a3..2f4b6b2372f 100644 --- a/packages/sdk/src/evm/index.ts +++ b/packages/sdk/src/evm/index.ts @@ -173,6 +173,7 @@ export { fetchSnapshotEntryForAddress } from "./common/claim-conditions/fetchSna export { getCachedAbiForContract } from "./common/abi"; export * from "./common/ens/resolveEns"; export * from "./common/ens/resolveAddress"; +export * from "./common/sign"; //#endregion @r/packages/sdk/src/evm/common/* //#region @r/packages/sdk/src/evm/constants/* diff --git a/packages/wallets/src/evm/wallets/smart-wallet.ts b/packages/wallets/src/evm/wallets/smart-wallet.ts index 8d2aabeedbf..1c8dbeb4e57 100644 --- a/packages/wallets/src/evm/wallets/smart-wallet.ts +++ b/packages/wallets/src/evm/wallets/smart-wallet.ts @@ -1,4 +1,5 @@ import { AbstractClientWallet, WalletOptions } from "./base"; +import { checkContractWalletSignature } from "./abstract"; import type { ConnectParams } from "../interfaces/connector"; import type { SmartWalletConfig, @@ -14,7 +15,8 @@ import { } from "@thirdweb-dev/sdk"; import { walletIds } from "../constants/walletIds"; import { getValidChainRPCs } from "@thirdweb-dev/chains"; -import { providers, utils } from "ethers"; +import { providers, utils, Bytes, Signer } from "ethers"; +import { signTypedDataInternal } from "@thirdweb-dev/sdk"; // export types and utils for convenience export type * from "../connectors/smart-wallet/types"; @@ -76,6 +78,67 @@ export class SmartWallet extends AbstractClientWallet< return this.connector?.personalWallet; } + /** + * @returns the signature of the message + */ + public async signMessage(message: Bytes | string): Promise { + // Deploy smart wallet if needed + const connector = await this.getConnector(); + await connector.deployIfNeeded(); + + const erc4337Signer = await this.getSigner(); + const chainId = await erc4337Signer.getChainId(); + const address = await connector.getAddress(); + + /** + * We first try to sign the EIP-712 typed data i.e. the message mixed with the smart wallet's domain separator. + * If this fails, we fallback to the legacy signing method. + */ + try { + const result = await signTypedDataInternal( + erc4337Signer, + { + name: "Account", + version: "1", + chainId, + verifyingContract: address, + }, + { AccountMessage: [{ name: "message", type: "bytes" }] }, + { + message: utils.defaultAbiCoder.encode( + ["bytes32"], + [utils.hashMessage(message)], + ), + }, + ); + + const isValid = await checkContractWalletSignature( + message as string, + result.signature, + address, + chainId, + ); + + if (!isValid) { + throw new Error("Invalid signature"); + } + + return result.signature; + } catch { + return await this.signMessageLegacy(erc4337Signer, message); + } + } + + /** + * @returns the signature of the message (for legacy EIP-1271 signature verification) + */ + private async signMessageLegacy( + signer: Signer, + message: Bytes | string, + ): Promise { + return await signer.signMessage(message); + } + /** * Check whether the connected signer can execute a given transaction using the smart wallet. * @param transaction - the transaction to execute using the smart wallet.