-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathinsert-transaction.ts
162 lines (147 loc) · 5.3 KB
/
insert-transaction.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { StatusCodes } from "http-status-codes";
import { randomUUID } from "node:crypto";
import { TransactionDB } from "../../../shared/db/transactions/db";
import {
getWalletDetails,
isSmartBackendWallet,
WalletDetailsError,
type ParsedWalletDetails,
} from "../../../shared/db/wallets/get-wallet-details";
import { createCustomError } from "../../../server/middleware/error";
import { SendTransactionQueue } from "../../../worker/queues/send-transaction-queue";
import { getChecksumAddress } from "../primitive-types";
import { recordMetrics } from "../prometheus";
import { reportUsage } from "../usage";
import { doSimulateTransaction } from "./simulate-queued-transaction";
import type { InsertedTransaction, QueuedTransaction } from "./types";
interface InsertTransactionData {
insertedTransaction: InsertedTransaction;
idempotencyKey?: string;
shouldSimulate?: boolean;
}
/**
* Enqueue a transaction to be submitted onchain.
*
* @param args
* @returns queueId
*/
export const insertTransaction = async (
args: InsertTransactionData,
): Promise<string> => {
const { insertedTransaction, idempotencyKey, shouldSimulate = false } = args;
// The queueId uniquely represents an enqueued transaction.
// It's also used as the idempotency key (default = no idempotence).
let queueId: string = randomUUID();
if (idempotencyKey) {
queueId = idempotencyKey;
if (await TransactionDB.exists(queueId)) {
// No-op. Return the existing queueId.
return queueId;
}
}
let queuedTransaction: QueuedTransaction = {
...insertedTransaction,
status: "queued",
queueId,
queuedAt: new Date(),
resendCount: 0,
from: getChecksumAddress(insertedTransaction.from),
to: getChecksumAddress(insertedTransaction.to),
signerAddress: getChecksumAddress(insertedTransaction.signerAddress),
accountAddress: getChecksumAddress(insertedTransaction.accountAddress),
accountSalt: insertedTransaction.accountSalt,
target: getChecksumAddress(insertedTransaction.target),
sender: getChecksumAddress(insertedTransaction.sender),
value: insertedTransaction.value ?? 0n,
};
let walletDetails: ParsedWalletDetails | undefined;
try {
walletDetails = await getWalletDetails({
address: queuedTransaction.from,
});
// when using the v5 SDK with smart backend wallets, the following values are not set correctly:
// isUserOp is set to false
// account address is blank or the user provided value (this should be the SBW account address)
// from is set to the SBW account address (this should be the SBW signer address)
// these values need to be corrected so the worker can process the transaction
if (isSmartBackendWallet(walletDetails)) {
if (queuedTransaction.accountAddress) {
throw createCustomError(
"Smart backend wallets do not support interacting with other smart accounts",
StatusCodes.BAD_REQUEST,
"INVALID_SMART_BACKEND_WALLET_INTERACTION",
);
}
queuedTransaction = {
...queuedTransaction,
isUserOp: true,
signerAddress: walletDetails.accountSignerAddress,
from: walletDetails.accountSignerAddress,
accountAddress: queuedTransaction.from,
target: queuedTransaction.to,
accountFactoryAddress: walletDetails.accountFactoryAddress ?? undefined,
entrypointAddress: walletDetails.entrypointAddress ?? undefined,
};
}
} catch (e) {
if (e instanceof WalletDetailsError) {
// do nothing. The this is a smart backend wallet using a v4 endpoint
} else {
// if other type of error, rethrow
throw e;
}
}
if (!walletDetails && queuedTransaction.accountAddress) {
try {
walletDetails = await getWalletDetails({
address: queuedTransaction.accountAddress,
});
// when using v4 SDK with smart backend wallets, the following values are not set correctly:
// entrypointAddress is not set
// accountFactoryAddress is not set
if (walletDetails && isSmartBackendWallet(walletDetails)) {
queuedTransaction = {
...queuedTransaction,
entrypointAddress: walletDetails.entrypointAddress ?? undefined,
accountFactoryAddress:
walletDetails.accountFactoryAddress ?? undefined,
};
}
} catch (e: unknown) {
// if wallet details are not found for this either, this backend wallet does not exist at all
if (e instanceof WalletDetailsError) {
throw createCustomError(
"Account not found",
StatusCodes.BAD_REQUEST,
"ACCOUNT_NOT_FOUND",
);
}
throw e;
}
}
// Simulate the transaction.
if (shouldSimulate) {
const error = await doSimulateTransaction(queuedTransaction);
if (error) {
throw createCustomError(
`Simulation failed: ${error.replace(/[\r\n]+/g, " --- ")}`,
400,
"BAD_REQUEST",
);
}
}
await TransactionDB.set(queuedTransaction);
await SendTransactionQueue.add({
queueId: queuedTransaction.queueId,
resendCount: 0,
});
reportUsage([{ action: "queue_tx", input: queuedTransaction }]);
recordMetrics({
event: "transaction_queued",
params: {
chainId: queuedTransaction.chainId.toString(),
walletAddress: queuedTransaction.from,
},
});
return queueId;
};