diff --git a/aa-sdk/ethers/src/__tests__/provider-adapter.test.ts b/aa-sdk/ethers/src/__tests__/provider-adapter.test.ts index f7241e38de..a73f31ac34 100644 --- a/aa-sdk/ethers/src/__tests__/provider-adapter.test.ts +++ b/aa-sdk/ethers/src/__tests__/provider-adapter.test.ts @@ -1,15 +1,22 @@ +import dotenv from "dotenv"; + import { createLightAccount } from "@account-kit/smart-contracts"; import { Wallet } from "@ethersproject/wallet"; import { Alchemy, Network, type AlchemyProvider } from "alchemy-sdk"; -import { http } from "viem"; -import { polygonMumbai } from "viem/chains"; +import { createPublicClient, http, type Hex } from "viem"; +import { sepolia } from "viem/chains"; import { EthersProviderAdapter } from "../../src/provider-adapter.js"; import { convertWalletToAccountSigner } from "../utils.js"; +dotenv.config(); + +const endpoint = + process.env.VITEST_SEPOLIA_FORK_URL ?? + "https://ethereum-sepolia-rpc.publicnode.com"; describe("Simple Account Tests", async () => { const alchemy = new Alchemy({ apiKey: "test", - network: Network.MATIC_MUMBAI, + network: Network.ETH_SEPOLIA, }); // demo mnemonic from viem docs const dummyMnemonic = @@ -19,13 +26,25 @@ describe("Simple Account Tests", async () => { it("should correctly sign the message", async () => { const provider = await givenConnectedProvider({ alchemyProvider, signer }); + const message = "hello world"; + const signature = (await provider.signMessage(message)) as Hex; + + // We must use a public client, rather than an account client, to verify the message, because AA-SDK incorrectly attaches the account address as a "from" field to all actions taken by that client, including the `eth_call` used internally by viem's signature verifier logic. Per EIP-684, contract creation reverts on non-zero nonce, and the `eth_call`'s from field implicitly increases the nonce of the account contract, causing the contract creation to revert. + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`${endpoint}`), + }); + expect( - await provider.signMessage( - "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b" - ) - ).toBe( - "0x007ecc361d63ab82d89faeecfb79d40127f376c1ef3d545aeec3578eadce9d0c405a4d1ae6177bdebdc8413065014f735ee98da9643cc0e25c07a7423b694f8ae71b" - ); + await publicClient.verifyMessage({ + address: provider.account.address, + message, + signature, + // todo: This provider doesn't support signMessageWith6492, so we have to manually provider the factory data to the verifier. Add support here for 6492 signature generation. + factory: await provider.account.getFactoryAddress(), + factoryData: await provider.account.getFactoryData(), + }) + ).toBe(true); }); }); @@ -36,7 +55,7 @@ const givenConnectedProvider = async ({ alchemyProvider: AlchemyProvider; signer: Wallet; }) => { - const chain = polygonMumbai; + const chain = sepolia; return EthersProviderAdapter.fromEthersProvider( alchemyProvider, @@ -44,9 +63,8 @@ const givenConnectedProvider = async ({ ).connectToAccount( await createLightAccount({ chain, - accountAddress: "0x856185aedfab56809e6686d2d6d0c039d615bd9c", signer: convertWalletToAccountSigner(signer), - transport: http(`${alchemyProvider.connection.url}`), + transport: http(`${endpoint}`), }) ); }; diff --git a/account-kit/smart-contracts/src/light-account/accounts/base.ts b/account-kit/smart-contracts/src/light-account/accounts/base.ts index 95aeb9b242..e58a86bdd1 100644 --- a/account-kit/smart-contracts/src/light-account/accounts/base.ts +++ b/account-kit/smart-contracts/src/light-account/accounts/base.ts @@ -139,7 +139,10 @@ export async function createLightAccountBase< }); }; - const signWith1271WrapperV1 = async (hashedMessage: Hex): Promise => { + const signWith1271Wrapper = async ( + hashedMessage: Hex, + version: string + ): Promise => { return signer.signTypedData({ // EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) // https://github.com/alchemyplatform/light-account/blob/main/src/LightAccount.sol#L236 @@ -147,7 +150,7 @@ export async function createLightAccountBase< chainId: Number(client.chain.id), name: type, verifyingContract: accountAddress, - version: "1", + version, }, types: { LightAccountMessage: [{ name: "message", type: "bytes" }], @@ -207,9 +210,12 @@ export async function createLightAccountBase< case "v1.0.2": throw new Error(`${type} ${String(version)} doesn't support 1271`); case "v1.1.0": - return signWith1271WrapperV1(hashMessage(message)); + return signWith1271Wrapper(hashMessage(message), "1"); case "v2.0.0": - const signature = await signWith1271WrapperV1(hashMessage(message)); + const signature = await signWith1271Wrapper( + hashMessage(message), + "2" + ); // TODO: handle case where signer is an SCA. return concat([SignatureType.EOA, signature]); default: @@ -227,9 +233,12 @@ export async function createLightAccountBase< `Version ${String(version)} of LightAccount doesn't support 1271` ); case "v1.1.0": - return signWith1271WrapperV1(hashTypedData(params)); + return signWith1271Wrapper(hashTypedData(params), "1"); case "v2.0.0": - const signature = await signWith1271WrapperV1(hashTypedData(params)); + const signature = await signWith1271Wrapper( + hashTypedData(params), + "2" + ); // TODO: handle case where signer is an SCA. return concat([SignatureType.EOA, signature]); default: diff --git a/account-kit/smart-contracts/src/light-account/clients/client.test.ts b/account-kit/smart-contracts/src/light-account/clients/client.test.ts index 239576df01..0c58a7c142 100644 --- a/account-kit/smart-contracts/src/light-account/clients/client.test.ts +++ b/account-kit/smart-contracts/src/light-account/clients/client.test.ts @@ -9,7 +9,7 @@ import { type UserOperationOverrides, type UserOperationStruct, } from "@aa-sdk/core"; -import { custom, parseEther, type Address } from "viem"; +import { custom, parseEther, type Address, publicActions } from "viem"; import { setBalance } from "viem/actions"; import { resetBalance } from "~test/accounts.js"; import { accounts } from "~test/constants.js"; @@ -65,32 +65,37 @@ describe("Light Account Tests", () => { ); it.each(versions)("should correctly sign the message", async (version) => { - const { account } = await givenConnectedProvider({ + const provider = await givenConnectedProvider({ signer, version, }); - const message = - "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b"; + + const { account } = provider; + + const message = "hello world"; switch (version) { - case "v1.0.1": - expect(await account.signMessage({ message })).toBe( - "0x53f3169bd14b5ade19a0488bf04ba94a1afa46549014febf3d20d2850a92c98f200c729d723b0592c8b4cb64c0423bb40c5a7fb49f69f4ef5f71ecc672e782e71b" - ); - break; case "v1.0.2": await expect(account.signMessage({ message })).rejects.toThrowError( "LightAccount v1.0.2 doesn't support 1271" ); break; + case "v1.0.1": case "v1.1.0": - expect(await account.signMessage({ message })).toBe( - "0xddb694a4f22cb929476a39f63aae047c2ccd6a12df17c91ff2068d8679f7d2e57b019263691eae350ce53bc6f958d4f2200571614eab8343af9c382aa90579371c" - ); - break; case "v2.0.0": - expect(await account.signMessage({ message })).toBe( - "0x00823ee659d8f86abaee06bb36c4d84bfefa2938af2835ff62564e9559bc671d0c0e8603a5aa3ed9603b28a137eaf491b64c55ed73007c12efebcfd9a82cb181221b" - ); + { + const signature = await account.signMessageWith6492({ + message, + }); + + // We must use a public client, rather than an account client, to verify the message, because AA-SDK incorrectly attaches the account address as a "from" field to all actions taken by that client, including the `eth_call` used internally by viem's signature verifier logic. Per EIP-684, contract creation reverts on non-zero nonce, and the `eth_call`'s from field implicitly increases the nonce of the account contract, causing the contract creation to revert. + expect( + await client.extend(publicActions).verifyMessage({ + address: account.address, + message, + signature, + }) + ).toBe(true); + } break; default: throw new Error(`Unknown version ${version}`); @@ -112,25 +117,26 @@ describe("Light Account Tests", () => { }, } as const; switch (version) { - case "v1.0.1": - expect(await account.signTypedData(typedData)).toBe( - "0x974c40bb8038696abf26fc9134676b9afd44bcfbe6821acd523193d80a354cfc7f3e8fdc28358c5551231156cec29e1c234ffe9cad09d51d633736d582d997811b" - ); - break; case "v1.0.2": await expect(account.signTypedData(typedData)).rejects.toThrowError( "Version v1.0.2 of LightAccount doesn't support 1271" ); break; case "v1.1.0": - expect(await account.signTypedData(typedData)).toBe( - "0x7d42ec333209a34a4a64d5bcb40ba67bc8ed3751f30898be60c1eba78d127e0e06b426a1faef69aa683f92aecc6a8e2292689b03e19f41417dcfa875410bd09d1c" - ); - break; + case "v1.0.1": case "v2.0.0": - expect(await account.signTypedData(typedData)).toBe( - "0x00813b860dce6079750e9383e932412620f6bc206261fed24bc450a1c858acb35912eb082c6fe69daf47f662b88941a9f67da425fa7084f2d93eb5bd2d2705905c1b" - ); + { + const signature = await account.signTypedDataWith6492(typedData); + + // See above comment for context on the duplicate client. + expect( + await client.extend(publicActions).verifyTypedData({ + address: account.address, + signature, + ...typedData, + }) + ).toBe(true); + } break; default: throw new Error(`Unknown version ${version}`); diff --git a/account-kit/smart-contracts/src/light-account/clients/multiOwnerLightAccount.test.ts b/account-kit/smart-contracts/src/light-account/clients/multiOwnerLightAccount.test.ts index 0878fc951b..0b48893a4e 100644 --- a/account-kit/smart-contracts/src/light-account/clients/multiOwnerLightAccount.test.ts +++ b/account-kit/smart-contracts/src/light-account/clients/multiOwnerLightAccount.test.ts @@ -6,7 +6,7 @@ import { type Address, type SmartAccountSigner, } from "@aa-sdk/core"; -import { custom, parseEther } from "viem"; +import { custom, parseEther, publicActions } from "viem"; import { generatePrivateKey } from "viem/accounts"; import { mine, setBalance } from "viem/actions"; import { accounts } from "~test/constants.js"; @@ -102,30 +102,41 @@ describe("MultiOwner Light Account Tests", () => { signer: undeployedSigner, }); + const typedData = { + types: { + Request: [{ name: "hello", type: "string" }], + }, + primaryType: "Request", + message: { + hello: "world", + }, + } as const; + + const signature = await account.signTypedDataWith6492(typedData); + expect( - await account.signTypedDataWith6492({ - types: { - Request: [{ name: "hello", type: "string" }], - }, - primaryType: "Request", - message: { - hello: "world", - }, + await client.extend(publicActions).verifyTypedData({ + address: account.address, + signature, + ...typedData, }) - ).toMatchInlineSnapshot( - `"0x000000000000000000000000000000000019d2ee9f2729a65afe20bb0020aefc000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000084b54c02f20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008391de4cacfb91f1cf953cf345948d92e137b6b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200e6821f95b4baff474a8f469467ac7c8f41c916932db259b8c1ca2d58ba327c4116fe018da508b7a0e65646a1c7b0ea6b8718ffbd6e1b184f79c846d817d727941c0000000000000000000000000000000000000000000000000000000000006492649264926492649264926492649264926492649264926492649264926492"` - ); + ).toBe(true); }); it("should sign message with 6492 successfully for undeployed account", async () => { const { account } = await givenConnectedProvider({ signer: undeployedSigner, }); + const message = "test"; + + const signature = await account.signMessageWith6492({ message }); expect( - await account.signMessageWith6492({ message: "test" }) - ).toMatchInlineSnapshot( - `"0x000000000000000000000000000000000019d2ee9f2729a65afe20bb0020aefc000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000084b54c02f20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008391de4cacfb91f1cf953cf345948d92e137b6b90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000420014415f75bc2032cac0daac2ac988561fbb27e8f936eb730a920bb34003d4c2a23a1e9356aabf1eeda097e8af5f107fe0222742bce6ca57709fe240f9e1bb2a991b0000000000000000000000000000000000000000000000000000000000006492649264926492649264926492649264926492649264926492649264926492"` - ); + await client.extend(publicActions).verifyMessage({ + address: account.address, + message, + signature, + }) + ).toBe(true); }); it("should get on-chain owner addresses successfully", async () => {