-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathsend-transaction-batch-atomic.ts
147 lines (137 loc) · 5.15 KB
/
send-transaction-batch-atomic.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { getAddress, type Address, type Hex } from "thirdweb";
import { insertTransaction } from "../../../shared/utils/transaction/insert-transaction";
import {
requestQuerystringSchema,
standardResponseSchema,
transactionWritesResponseSchema,
} from "../../schemas/shared-api-schemas";
import {
maybeAddress,
walletChainParamSchema,
walletWithAAHeaderSchema,
} from "../../schemas/wallet";
import { getChainIdFromChain } from "../../utils/chain";
import {
getWalletDetails,
isSmartBackendWallet,
type ParsedWalletDetails,
WalletDetailsError,
} from "../../../shared/db/wallets/get-wallet-details";
import { createCustomError } from "../../middleware/error";
import { RawTransactionParamsSchema } from "../../schemas/transaction/raw-transaction-parms";
const requestBodySchema = Type.Object({
transactions: Type.Array(RawTransactionParamsSchema, {
minItems: 1,
}),
});
export async function sendTransactionBatchAtomicRoute(
fastify: FastifyInstance,
) {
fastify.route<{
Params: Static<typeof walletChainParamSchema>;
Body: Static<typeof requestBodySchema>;
Reply: Static<typeof transactionWritesResponseSchema>;
Querystring: Static<typeof requestQuerystringSchema>;
}>({
method: "POST",
url: "/backend-wallet/:chain/send-transaction-batch-atomic",
schema: {
summary: "Send a batch of raw transactions atomically",
description:
"Send a batch of raw transactions in a single UserOp. Transactions will be sent in-order and atomically. Can only be used with smart wallets.",
tags: ["Backend Wallet"],
operationId: "sendTransactionBatchAtomic",
params: walletChainParamSchema,
body: requestBodySchema,
headers: Type.Omit(walletWithAAHeaderSchema, ["x-transaction-mode"]),
querystring: requestQuerystringSchema,
response: {
...standardResponseSchema,
[StatusCodes.OK]: transactionWritesResponseSchema,
},
},
handler: async (request, reply) => {
const { chain } = request.params;
const {
"x-backend-wallet-address": fromAddress,
"x-idempotency-key": idempotencyKey,
"x-account-address": accountAddress,
"x-account-factory-address": accountFactoryAddress,
"x-account-salt": accountSalt,
} = request.headers as Static<typeof walletWithAAHeaderSchema>;
const chainId = await getChainIdFromChain(chain);
const shouldSimulate = request.query.simulateTx ?? false;
const transactionRequests = request.body.transactions;
const hasSmartHeaders = !!accountAddress;
// check that we either use SBW, or send using EOA with smart wallet headers
if (!hasSmartHeaders) {
let backendWallet: ParsedWalletDetails | undefined;
try {
backendWallet = await getWalletDetails({
address: fromAddress,
});
} catch (e: unknown) {
if (e instanceof WalletDetailsError) {
throw createCustomError(
`Failed to get wallet details for backend wallet ${fromAddress}. ${e.message}`,
StatusCodes.BAD_REQUEST,
"WALLET_DETAILS_ERROR",
);
}
}
if (!backendWallet) {
throw createCustomError(
"Failed to get wallet details for backend wallet. See: https://portal.thirdweb.com/engine/troubleshooting",
StatusCodes.INTERNAL_SERVER_ERROR,
"WALLET_DETAILS_ERROR",
);
}
if (!isSmartBackendWallet(backendWallet)) {
throw createCustomError(
"Backend wallet is not a smart wallet, and x-account-address is not provided. Either use a smart backend wallet or provide x-account-address. This endpoint can only be used with smart wallets.",
StatusCodes.BAD_REQUEST,
"BACKEND_WALLET_NOT_SMART",
);
}
}
if (transactionRequests.length === 0) {
throw createCustomError(
"No transactions provided",
StatusCodes.BAD_REQUEST,
"NO_TRANSACTIONS_PROVIDED",
);
}
const queueId = await insertTransaction({
insertedTransaction: {
...(hasSmartHeaders
? { isUserOp: true, signerAddress: getAddress(fromAddress) }
: { isUserOp: false }),
transactionMode: undefined,
chainId,
from: fromAddress as Address,
accountAddress: maybeAddress(accountAddress, "x-account-address"),
accountFactoryAddress: maybeAddress(
accountFactoryAddress,
"x-account-factory-address",
),
accountSalt: accountSalt,
batchOperations: transactionRequests.map((transactionRequest) => ({
to: transactionRequest.toAddress as Address | undefined,
data: transactionRequest.data as Hex,
value: BigInt(transactionRequest.value),
})),
},
shouldSimulate,
idempotencyKey,
});
reply.status(StatusCodes.OK).send({
result: {
queueId,
},
});
},
});
}