diff --git a/src/server/middleware/error.ts b/src/server/middleware/error.ts index 92dbf820..7b61b4ca 100644 --- a/src/server/middleware/error.ts +++ b/src/server/middleware/error.ts @@ -42,6 +42,13 @@ export const badChainError = (chain: string | number): CustomError => "INVALID_CHAIN", ); +export const badBigIntError = (variableName: string): CustomError => + createCustomError( + `Invalid BigInt: ${variableName}`, + StatusCodes.BAD_REQUEST, + "INVALID_BIGINT", + ); + const flipObject = (data: object) => Object.fromEntries(Object.entries(data).map(([key, value]) => [value, key])); diff --git a/src/server/routes/backend-wallet/send-transaction.ts b/src/server/routes/backend-wallet/send-transaction.ts index 13e2a8f3..3d783338 100644 --- a/src/server/routes/backend-wallet/send-transaction.ts +++ b/src/server/routes/backend-wallet/send-transaction.ts @@ -17,6 +17,10 @@ import { } from "../../schemas/wallet"; import { getChainIdFromChain } from "../../utils/chain"; import { parseTransactionOverrides } from "../../utils/transaction-overrides"; +import { + authorizationListSchema, + toParsedAuthorization, +} from "../../schemas/transaction/authorization"; const requestBodySchema = Type.Object({ toAddress: Type.Optional(AddressSchema), @@ -26,6 +30,7 @@ const requestBodySchema = Type.Object({ value: Type.String({ examples: ["10000000"], }), + authorizationList: authorizationListSchema, ...txOverridesSchema.properties, }); @@ -65,7 +70,8 @@ export async function sendTransaction(fastify: FastifyInstance) { }, handler: async (request, reply) => { const { chain } = request.params; - const { toAddress, data, value, txOverrides } = request.body; + const { toAddress, data, value, txOverrides, authorizationList } = + request.body; const { simulateTx } = request.query; const { "x-backend-wallet-address": fromAddress, @@ -110,6 +116,7 @@ export async function sendTransaction(fastify: FastifyInstance) { data: data as Hex, transactionMode: transactionMode, value: BigInt(value), + authorizationList: authorizationList?.map(toParsedAuthorization), ...parseTransactionOverrides(txOverrides), }, shouldSimulate: simulateTx, diff --git a/src/server/schemas/transaction/authorization.ts b/src/server/schemas/transaction/authorization.ts new file mode 100644 index 00000000..3f6e9953 --- /dev/null +++ b/src/server/schemas/transaction/authorization.ts @@ -0,0 +1,30 @@ +import { type Static, Type } from "@sinclair/typebox"; +import { AddressSchema } from "../address"; +import { requiredAddress } from "../wallet"; +import { requiredBigInt } from "../../../shared/utils/primitive-types"; + +export const authorizationSchema = Type.Object({ + address: AddressSchema, + chainId: Type.Integer(), + nonce: Type.String(), + r: Type.String(), + s: Type.String(), + yParity: Type.Number(), +}); + +export const authorizationListSchema = Type.Optional( + Type.Array(authorizationSchema), +); + +export const toParsedAuthorization = ( + authorization: Static, +) => { + return { + address: requiredAddress(authorization.address, "[Authorization List]"), + chainId: authorization.chainId, + nonce: requiredBigInt(authorization.nonce, "[Authorization List] -> nonce"), + r: requiredBigInt(authorization.r, "[Authorization List] -> r"), + s: requiredBigInt(authorization.s, "[Authorization List] -> s"), + yParity: authorization.yParity, + }; +}; diff --git a/src/server/utils/transaction-overrides.ts b/src/server/utils/transaction-overrides.ts index 443758ef..06575dc4 100644 --- a/src/server/utils/transaction-overrides.ts +++ b/src/server/utils/transaction-overrides.ts @@ -1,6 +1,5 @@ import type { Static } from "@sinclair/typebox"; import { maybeBigInt } from "../../shared/utils/primitive-types"; -import type { InsertedTransaction } from "../../shared/utils/transaction/types"; import type { txOverridesSchema, txOverridesWithValueSchema, @@ -10,7 +9,7 @@ export const parseTransactionOverrides = ( overrides: | Static["txOverrides"] | Static["txOverrides"], -): Partial => { +) => { if (!overrides) { return {}; } diff --git a/src/shared/utils/primitive-types.ts b/src/shared/utils/primitive-types.ts index 759902bc..5f7f30cc 100644 --- a/src/shared/utils/primitive-types.ts +++ b/src/shared/utils/primitive-types.ts @@ -1,10 +1,19 @@ import type { Address } from "thirdweb"; import { checksumAddress } from "thirdweb/utils"; +import { badBigIntError } from "../../server/middleware/error"; export const maybeBigInt = (val?: string) => (val ? BigInt(val) : undefined); export const maybeInt = (val?: string) => val ? Number.parseInt(val) : undefined; +export function requiredBigInt(val: string, variableName: string) { + try { + return BigInt(val); + } catch { + throw badBigIntError(variableName); + } +} + // These overloads hint TS at the response type (ex: Address if `val` is Address). export function normalizeAddress(val: Address): Address; export function normalizeAddress(val?: Address): Address | undefined; diff --git a/src/shared/utils/transaction/types.ts b/src/shared/utils/transaction/types.ts index 6b28b88d..920e23f4 100644 --- a/src/shared/utils/transaction/types.ts +++ b/src/shared/utils/transaction/types.ts @@ -1,4 +1,10 @@ -import type { Address, Hex, toSerializableTransaction } from "thirdweb"; +import { + signAuthorization, + SignedAuthorization, + type Address, + type Hex, + type toSerializableTransaction, +} from "thirdweb"; import type { TransactionType } from "viem"; // TODO: Replace with thirdweb SDK exported type when available. @@ -30,6 +36,7 @@ export type InsertedTransaction = { value?: bigint; data?: Hex; + authorizationList?: SignedAuthorization[]; functionName?: string; functionArgs?: unknown[];