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
85 changes: 38 additions & 47 deletions src/server/routes/backend-wallet/send-transaction.ts
Original file line number Diff line number Diff line change
@@ -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),
Expand Down Expand Up @@ -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<typeof walletWithAAHeaderSchema>;

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: {
Expand Down
39 changes: 33 additions & 6 deletions src/server/routes/backend-wallet/sign-typed-data.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
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";

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({
Expand All @@ -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<typeof walletHeaderSchema>;

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,
Expand Down
4 changes: 3 additions & 1 deletion src/server/routes/contract/write/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
9 changes: 4 additions & 5 deletions src/shared/utils/transaction/types.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down