Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/server/routes/backend-wallet/sign-typed-data.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<string, unknown>).EIP712Domain;
parsedPrimaryType =
ethers.utils._TypedDataEncoder.getPrimaryType(types);
}

const { account } = await walletDetailsToAccount({
walletDetails,
chain,
Expand All @@ -68,7 +78,7 @@ export async function signTypedData(fastify: FastifyInstance) {
const result = await account.signTypedData({
domain,
types,
primaryType,
primaryType: parsedPrimaryType,
message: value,
} as never);

Expand Down
78 changes: 78 additions & 0 deletions tests/e2e/tests/routes/sign-typed-data.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});