From 9965eb218589faa6f3e07516c7187b76744382b9 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 19 Mar 2025 20:47:09 +1300 Subject: [PATCH] Refactor transaction handling with prepareTransaction and queueTransaction --- .../routes/backend-wallet/send-transaction.ts | 85 +++++++++---------- .../routes/backend-wallet/sign-typed-data.ts | 39 +++++++-- src/server/routes/contract/write/write.ts | 4 +- src/shared/utils/transaction/types.ts | 9 +- 4 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/server/routes/backend-wallet/send-transaction.ts b/src/server/routes/backend-wallet/send-transaction.ts index 3d7833386..84bb80651 100644 --- a/src/server/routes/backend-wallet/send-transaction.ts +++ b/src/server/routes/backend-wallet/send-transaction.ts @@ -1,26 +1,29 @@ -import { Type, type Static } from "@sinclair/typebox"; +import { type Static, Type } from "@sinclair/typebox"; import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; -import type { Address, Hex } from "thirdweb"; -import { insertTransaction } from "../../../shared/utils/transaction/insert-transaction"; +import { type Hex, prepareTransaction } from "thirdweb"; +import { getChain } from "../../../shared/utils/chain"; +import { thirdwebClient } from "../../../shared/utils/sdk"; +import { queueTransaction } from "../../../shared/utils/transaction/queue-transation"; import { AddressSchema } from "../../schemas/address"; import { requestQuerystringSchema, standardResponseSchema, transactionWritesResponseSchema, } from "../../schemas/shared-api-schemas"; +import { + authorizationListSchema, + toParsedAuthorization, +} from "../../schemas/transaction/authorization"; import { txOverridesSchema } from "../../schemas/tx-overrides"; import { maybeAddress, + requiredAddress, walletChainParamSchema, walletWithAAHeaderSchema, } 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), @@ -78,51 +81,39 @@ export async function sendTransaction(fastify: FastifyInstance) { "x-idempotency-key": idempotencyKey, "x-account-address": accountAddress, "x-account-factory-address": accountFactoryAddress, + "x-account-salt": accountSalt, "x-transaction-mode": transactionMode, } = request.headers as Static; const chainId = await getChainIdFromChain(chain); + const chainObject = await getChain(chainId); + const { value: valueOverride, overrides } = + parseTransactionOverrides(txOverrides); + const transaction = prepareTransaction({ + client: thirdwebClient, + chain: chainObject, + to: toAddress, + data: data as Hex, + value: BigInt(value) || valueOverride, + authorizationList: authorizationList?.map(toParsedAuthorization), + ...overrides, + }); - let queueId: string; - if (accountAddress) { - queueId = await insertTransaction({ - insertedTransaction: { - isUserOp: true, - chainId, - from: fromAddress as Address, - to: toAddress as Address | undefined, - data: data as Hex, - value: BigInt(value), - accountAddress: accountAddress as Address, - signerAddress: fromAddress as Address, - target: toAddress as Address | undefined, - transactionMode: undefined, - accountFactoryAddress: maybeAddress( - accountFactoryAddress, - "x-account-factory-address", - ), - ...parseTransactionOverrides(txOverrides), - }, - shouldSimulate: simulateTx, - idempotencyKey, - }); - } else { - queueId = await insertTransaction({ - insertedTransaction: { - isUserOp: false, - chainId, - from: fromAddress as Address, - to: toAddress as Address | undefined, - data: data as Hex, - transactionMode: transactionMode, - value: BigInt(value), - authorizationList: authorizationList?.map(toParsedAuthorization), - ...parseTransactionOverrides(txOverrides), - }, - shouldSimulate: simulateTx, - idempotencyKey, - }); - } + const queueId = await queueTransaction({ + transaction, + fromAddress: requiredAddress(fromAddress, "x-backend-wallet-address"), + toAddress: maybeAddress(toAddress, "to"), + accountAddress: maybeAddress(accountAddress, "x-account-address"), + accountFactoryAddress: maybeAddress( + accountFactoryAddress, + "x-account-factory-address", + ), + accountSalt, + txOverrides, + idempotencyKey, + transactionMode, + shouldSimulate: simulateTx, + }); reply.status(StatusCodes.OK).send({ result: { diff --git a/src/server/routes/backend-wallet/sign-typed-data.ts b/src/server/routes/backend-wallet/sign-typed-data.ts index d668f60ed..6d0288b07 100644 --- a/src/server/routes/backend-wallet/sign-typed-data.ts +++ b/src/server/routes/backend-wallet/sign-typed-data.ts @@ -1,8 +1,12 @@ -import type { TypedDataSigner } from "@ethersproject/abstract-signer"; import { type Static, Type } from "@sinclair/typebox"; import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; -import { getWallet } from "../../../shared/utils/cache/get-wallet"; +import { arbitrumSepolia } from "thirdweb/chains"; +import { isSmartBackendWallet } from "../../../shared/db/wallets/get-wallet-details"; +import { getWalletDetails } from "../../../shared/db/wallets/get-wallet-details"; +import { walletDetailsToAccount } from "../../../shared/utils/account"; +import { getChain } from "../../../shared/utils/chain"; +import { createCustomError } from "../../middleware/error"; import { standardResponseSchema } from "../../schemas/shared-api-schemas"; import { walletHeaderSchema } from "../../schemas/wallet"; @@ -10,6 +14,8 @@ const requestBodySchema = Type.Object({ domain: Type.Object({}, { additionalProperties: true }), types: Type.Object({}, { additionalProperties: true }), value: Type.Object({}, { additionalProperties: true }), + primaryType: Type.Optional(Type.String()), + chainId: Type.Optional(Type.Number()), }); const responseBodySchema = Type.Object({ @@ -36,14 +42,35 @@ export async function signTypedData(fastify: FastifyInstance) { }, }, handler: async (request, reply) => { - const { domain, value, types } = request.body; + const { domain, value, types, chainId, primaryType } = request.body; const { "x-backend-wallet-address": walletAddress } = request.headers as Static; - const wallet = await getWallet({ chainId: 1, walletAddress }); + const walletDetails = await getWalletDetails({ + address: walletAddress, + }); + + if (isSmartBackendWallet(walletDetails) && !chainId) { + throw createCustomError( + "Chain ID is required for signing messages with smart wallets.", + StatusCodes.BAD_REQUEST, + "CHAIN_ID_REQUIRED", + ); + } + + const chain = chainId ? await getChain(chainId) : arbitrumSepolia; + + const { account } = await walletDetailsToAccount({ + walletDetails, + chain, + }); - const signer = (await wallet.getSigner()) as unknown as TypedDataSigner; - const result = await signer._signTypedData(domain, types, value); + const result = await account.signTypedData({ + domain, + types, + primaryType, + message: value, + } as never); reply.status(StatusCodes.OK).send({ result: result, diff --git a/src/server/routes/contract/write/write.ts b/src/server/routes/contract/write/write.ts index ba954ca4b..46c0aea2d 100644 --- a/src/server/routes/contract/write/write.ts +++ b/src/server/routes/contract/write/write.ts @@ -110,11 +110,13 @@ export async function writeToContract(fastify: FastifyInstance) { ); } + const { value, overrides } = parseTransactionOverrides(txOverrides); const transaction = prepareContractCall({ contract, method, params, - ...parseTransactionOverrides(txOverrides), + value, + ...overrides, }); const queueId = await queueTransaction({ diff --git a/src/shared/utils/transaction/types.ts b/src/shared/utils/transaction/types.ts index 920e23f4d..f45c947ba 100644 --- a/src/shared/utils/transaction/types.ts +++ b/src/shared/utils/transaction/types.ts @@ -1,9 +1,8 @@ -import { - signAuthorization, +import type { + Address, + Hex, SignedAuthorization, - type Address, - type Hex, - type toSerializableTransaction, + toSerializableTransaction, } from "thirdweb"; import type { TransactionType } from "viem";