1
- import { getBlock } from "@thirdweb-dev/sdk" ;
2
1
import { ERC4337EthersSigner } from "@thirdweb-dev/wallets/dist/declarations/src/evm/connectors/smart-wallet/lib/erc4337-signer" ;
2
+ import { providers } from "ethers" ;
3
+ import {
4
+ defineChain ,
5
+ eth_getBlockByNumber ,
6
+ eth_getTransactionByHash ,
7
+ eth_getTransactionReceipt ,
8
+ getRpcClient ,
9
+ } from "thirdweb" ;
3
10
import { prisma } from "../../db/client" ;
4
11
import { getSentUserOps } from "../../db/transactions/getSentUserOps" ;
5
12
import { updateTx } from "../../db/transactions/updateTx" ;
6
13
import { TransactionStatus } from "../../server/schemas/transaction" ;
7
14
import { getSdk } from "../../utils/cache/getSdk" ;
15
+ import { msSince } from "../../utils/date" ;
8
16
import { logger } from "../../utils/logger" ;
17
+ import {
18
+ thirdwebClient ,
19
+ toTransactionStatus ,
20
+ toTransactionType ,
21
+ } from "../../utils/sdk" ;
9
22
import {
10
23
ReportUsageParams ,
11
24
UsageEventTxActionEnum ,
12
25
reportUsage ,
13
26
} from "../../utils/usage" ;
14
27
import { WebhookData , sendWebhooks } from "../../utils/webhook" ;
15
28
29
+ const CANCEL_DEADLINE_MS = 1000 * 60 * 60 ; // 1 hour
30
+
16
31
export const updateMinedUserOps = async ( ) => {
17
32
try {
18
33
const sendWebhookForQueueIds : WebhookData [ ] = [ ] ;
19
34
const reportUsageForQueueIds : ReportUsageParams [ ] = [ ] ;
20
35
await prisma . $transaction (
21
36
async ( pgtx ) => {
22
37
const userOps = await getSentUserOps ( { pgtx } ) ;
23
-
24
38
if ( userOps . length === 0 ) {
25
39
return ;
26
40
}
27
41
28
- // TODO: Improve spaghetti code...
29
- const updatedUserOps = (
30
- await Promise . all (
31
- userOps . map ( async ( userOp ) => {
32
- const sdk = await getSdk ( {
33
- chainId : parseInt ( userOp . chainId ! ) ,
34
- walletAddress : userOp . signerAddress ! ,
35
- accountAddress : userOp . accountAddress ! ,
36
- } ) ;
37
- const signer = sdk . getSigner ( ) as ERC4337EthersSigner ;
42
+ const promises = userOps . map ( async ( userOp ) => {
43
+ try {
44
+ if (
45
+ ! userOp . sentAt ||
46
+ ! userOp . signerAddress ||
47
+ ! userOp . accountAddress ||
48
+ ! userOp . userOpHash
49
+ ) {
50
+ return ;
51
+ }
38
52
39
- const userOpReceipt =
40
- await signer . smartAccountAPI . getUserOpReceipt (
41
- signer . httpRpcClient ,
42
- userOp . userOpHash ! ,
43
- 3000 ,
44
- ) ;
53
+ const sdk = await getSdk ( {
54
+ chainId : parseInt ( userOp . chainId ) ,
55
+ walletAddress : userOp . signerAddress ,
56
+ accountAddress : userOp . accountAddress ,
57
+ } ) ;
58
+ const signer = sdk . getSigner ( ) as ERC4337EthersSigner ;
59
+ let userOpReceipt : providers . TransactionReceipt | undefined ;
60
+ try {
61
+ // Get userOp receipt.
62
+ // If no receipt, try again later (or cancel userOps after 1 hour).
63
+ // Else the transaction call was submitted to mempool.
64
+ userOpReceipt = await signer . smartAccountAPI . getUserOpReceipt (
65
+ signer . httpRpcClient ,
66
+ userOp . userOpHash ,
67
+ 3_000 , // 3 seconds
68
+ ) ;
69
+ } catch ( error ) {
70
+ // Exception is thrown when userOp is not found/null
71
+ logger ( {
72
+ service : "worker" ,
73
+ level : "error" ,
74
+ queueId : userOp . id ,
75
+ message : "Failed to get receipt for UserOp" ,
76
+ error,
77
+ } ) ;
78
+ }
45
79
46
- if ( ! userOpReceipt ) {
47
- // If no receipt was received, return undefined to filter out tx
48
- return undefined ;
80
+ if ( ! userOpReceipt ) {
81
+ if ( msSince ( userOp . sentAt ) > CANCEL_DEADLINE_MS ) {
82
+ await updateTx ( {
83
+ pgtx,
84
+ queueId : userOp . id ,
85
+ data : {
86
+ status : TransactionStatus . Errored ,
87
+ errorMessage : "Transaction timed out." ,
88
+ } ,
89
+ } ) ;
49
90
}
50
- const _sdk = await getSdk ( {
51
- chainId : parseInt ( userOp . chainId ! ) ,
52
- } ) ;
91
+ return ;
92
+ }
53
93
54
- const tx = await signer . provider ! . getTransaction (
55
- userOpReceipt . transactionHash ,
56
- ) ;
57
- const txReceipt = await _sdk
58
- . getProvider ( )
59
- . getTransactionReceipt ( tx . hash ) ;
60
- const minedAt = new Date (
61
- (
62
- await getBlock ( {
63
- block : tx . blockNumber ! ,
64
- network : sdk . getProvider ( ) ,
65
- } )
66
- ) . timestamp * 1000 ,
67
- ) ;
94
+ const chain = defineChain ( parseInt ( userOp . chainId ) ) ;
95
+ const rpcRequest = getRpcClient ( {
96
+ client : thirdwebClient ,
97
+ chain,
98
+ } ) ;
68
99
69
- return {
70
- ...userOp ,
71
- blockNumber : tx . blockNumber ! ,
72
- minedAt,
73
- onChainTxStatus : txReceipt . status ,
74
- transactionHash : txReceipt . transactionHash ,
75
- transactionType : tx . type ,
76
- gasLimit : tx . gasLimit . toString ( ) ,
77
- maxFeePerGas : tx . maxFeePerGas ?. toString ( ) ,
78
- maxPriorityFeePerGas : tx . maxPriorityFeePerGas ?. toString ( ) ,
79
- provider : signer . httpRpcClient . bundlerUrl ,
80
- } ;
81
- } ) ,
82
- )
83
- ) . filter ( ( userOp ) => ! ! userOp ) ;
100
+ // Get the transaction receipt.
101
+ // If no receipt, try again later.
102
+ // Else the transaction call was confirmed onchain.
103
+ const transaction = await eth_getTransactionByHash ( rpcRequest , {
104
+ hash : userOpReceipt . transactionHash as `0x${string } `,
105
+ } ) ;
106
+ const transactionReceipt = await eth_getTransactionReceipt (
107
+ rpcRequest ,
108
+ { hash : transaction . hash } ,
109
+ ) ;
110
+ if ( ! transactionReceipt ) {
111
+ // If no receipt, try again later.
112
+ return ;
113
+ }
84
114
85
- await Promise . all (
86
- updatedUserOps . map ( async ( userOp ) => {
115
+ let minedAt = new Date ( ) ;
116
+ try {
117
+ const block = await eth_getBlockByNumber ( rpcRequest , {
118
+ blockNumber : transactionReceipt . blockNumber ,
119
+ includeTransactions : false ,
120
+ } ) ;
121
+ minedAt = new Date ( Number ( block . timestamp ) * 1000 ) ;
122
+ } catch ( e ) { }
123
+
124
+ // Update the userOp transaction as mined.
87
125
await updateTx ( {
88
126
pgtx,
89
- queueId : userOp ! . id ,
127
+ queueId : userOp . id ,
90
128
data : {
91
129
status : TransactionStatus . Mined ,
92
- minedAt : userOp ! . minedAt ,
93
- blockNumber : userOp ! . blockNumber ,
94
- onChainTxStatus : userOp ! . onChainTxStatus ,
95
- transactionHash : userOp ! . transactionHash ,
96
- transactionType : userOp ! . transactionType || undefined ,
97
- gasLimit : userOp ! . gasLimit || undefined ,
98
- maxFeePerGas : userOp ! . maxFeePerGas || undefined ,
99
- maxPriorityFeePerGas : userOp ! . maxPriorityFeePerGas || undefined ,
100
- gasPrice : userOp ! . gasPrice || undefined ,
130
+ minedAt,
131
+ blockNumber : Number ( transactionReceipt . blockNumber ) ,
132
+ onChainTxStatus : toTransactionStatus ( transactionReceipt . status ) ,
133
+ transactionHash : transactionReceipt . transactionHash ,
134
+ transactionType : toTransactionType ( transaction . type ) ,
135
+ gasLimit : userOp . gasLimit ?? undefined ,
136
+ maxFeePerGas : transaction . maxFeePerGas ?. toString ( ) ,
137
+ maxPriorityFeePerGas :
138
+ transaction . maxPriorityFeePerGas ?. toString ( ) ,
139
+ gasPrice : transaction . gasPrice ?. toString ( ) ,
101
140
} ,
102
141
} ) ;
103
142
104
143
logger ( {
105
144
service : "worker" ,
106
145
level : "info" ,
107
- queueId : userOp ! . id ,
108
- message : ` Updated with receipt` ,
146
+ queueId : userOp . id ,
147
+ message : " Updated with receipt" ,
109
148
} ) ;
110
149
sendWebhookForQueueIds . push ( {
111
- queueId : userOp ! . id ,
150
+ queueId : userOp . id ,
112
151
status : TransactionStatus . Mined ,
113
152
} ) ;
114
153
reportUsageForQueueIds . push ( {
115
154
input : {
116
- fromAddress : userOp ! . fromAddress || undefined ,
117
- toAddress : userOp ! . toAddress || undefined ,
118
- value : userOp ! . value || undefined ,
119
- chainId : userOp ! . chainId || undefined ,
120
- userOpHash : userOp ! . userOpHash || undefined ,
121
- onChainTxStatus : userOp ! . onChainTxStatus ,
122
- functionName : userOp ! . functionName || undefined ,
123
- extension : userOp ! . extension || undefined ,
124
- provider : userOp ! . provider || undefined ,
125
- msSinceSend :
126
- userOp ! . minedAt . getTime ( ) - userOp ! . sentAt ! . getTime ( ) ,
155
+ fromAddress : userOp . fromAddress ?? undefined ,
156
+ toAddress : userOp . toAddress ?? undefined ,
157
+ value : userOp . value ?? undefined ,
158
+ chainId : userOp . chainId ,
159
+ userOpHash : userOp . userOpHash ?? undefined ,
160
+ onChainTxStatus : toTransactionStatus ( transactionReceipt . status ) ,
161
+ functionName : userOp . functionName ?? undefined ,
162
+ extension : userOp . extension ?? undefined ,
163
+ provider : signer . httpRpcClient . bundlerUrl ,
164
+ msSinceSend : msSince ( userOp . sentAt ! ) ,
127
165
} ,
128
166
action : UsageEventTxActionEnum . MineTx ,
129
167
} ) ;
130
- } ) ,
131
- ) ;
168
+ } catch ( err ) {
169
+ logger ( {
170
+ service : "worker" ,
171
+ level : "error" ,
172
+ queueId : userOp . id ,
173
+ message : "Failed to update receipt for UserOp " ,
174
+ error : err ,
175
+ } ) ;
176
+ }
177
+ } ) ;
178
+
179
+ await Promise . all ( promises ) ;
132
180
} ,
133
181
{
134
- timeout : 5 * 60000 ,
182
+ timeout : 5 * 60 * 1000 , // 5 minutes
135
183
} ,
136
184
) ;
137
185
@@ -141,9 +189,8 @@ export const updateMinedUserOps = async () => {
141
189
logger ( {
142
190
service : "worker" ,
143
191
level : "error" ,
144
- message : ` Failed to update receipts` ,
192
+ message : " Failed to batch update receipts" ,
145
193
error : err ,
146
194
} ) ;
147
- return ;
148
195
}
149
196
} ;
0 commit comments