diff --git a/src/server/routes/backend-wallet/sign-typed-data.ts b/src/server/routes/backend-wallet/sign-typed-data.ts index 6d0288b0..42eeb652 100644 --- a/src/server/routes/backend-wallet/sign-typed-data.ts +++ b/src/server/routes/backend-wallet/sign-typed-data.ts @@ -1,4 +1,5 @@ import { type Static, Type } from "@sinclair/typebox"; +import { ethers } from "ethers"; import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; import { arbitrumSepolia } from "thirdweb/chains"; @@ -60,6 +61,15 @@ export async function signTypedData(fastify: FastifyInstance) { const chain = chainId ? await getChain(chainId) : arbitrumSepolia; + let parsedPrimaryType = primaryType; + if (!parsedPrimaryType) { + // try to detect the primary type, which requires removing the EIP712Domain type + // biome-ignore lint/performance/noDelete: need to delete explicitely + delete (types as unknown as Record).EIP712Domain; + parsedPrimaryType = + ethers.utils._TypedDataEncoder.getPrimaryType(types); + } + const { account } = await walletDetailsToAccount({ walletDetails, chain, @@ -68,7 +78,7 @@ export async function signTypedData(fastify: FastifyInstance) { const result = await account.signTypedData({ domain, types, - primaryType, + primaryType: parsedPrimaryType, message: value, } as never); diff --git a/tests/e2e/tests/routes/sign-typed-data.test.ts b/tests/e2e/tests/routes/sign-typed-data.test.ts new file mode 100644 index 00000000..f6b40179 --- /dev/null +++ b/tests/e2e/tests/routes/sign-typed-data.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, test } from "bun:test"; +import { signTypedData } from "thirdweb/utils"; +import { ANVIL_PKEY_A } from "../../utils/wallets"; +import { setup } from "../setup"; + +describe("signTypedDataRoute", () => { + const data = { + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0x0000000000000000000000000000000000000000", + }, + message: { + contents: "Hello, Bob!", + from: { + name: "Alice", + }, + }, + primaryType: "Mail", + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + Mail: [ + { name: "contents", type: "string" }, + { name: "from", type: "Person" }, + ], + Person: [{ name: "name", type: "string" }], + }, + } as const; + + test("Sign typed data", async () => { + const { engine, backendWallet } = await setup(); + + const res = await engine.backendWallet.signTypedData(backendWallet, { + domain: data.domain, + value: data.message, + types: data.types, + primaryType: data.primaryType, + }); + + const expected = signTypedData({ + // @ts-expect-error - bigint serialization + domain: data.domain, + message: data.message, + types: data.types, + primaryType: data.primaryType, + privateKey: ANVIL_PKEY_A, + }); + + expect(res.result).toEqual(expected); + }); + + test("Sign typed data without primary type", async () => { + const { engine, backendWallet } = await setup(); + + const res = await engine.backendWallet.signTypedData(backendWallet, { + domain: data.domain, + value: data.message, + types: data.types, + }); + + const expected = signTypedData({ + // @ts-expect-error - bigint serialization + domain: data.domain, + message: data.message, + types: data.types, + primaryType: data.primaryType, + privateKey: ANVIL_PKEY_A, + }); + + expect(res.result).toEqual(expected); + }); +});