1- import { getBlock } from "@thirdweb-dev/sdk" ;
21import { 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" ;
310import { prisma } from "../../db/client" ;
411import { getSentUserOps } from "../../db/transactions/getSentUserOps" ;
512import { updateTx } from "../../db/transactions/updateTx" ;
613import { TransactionStatus } from "../../server/schemas/transaction" ;
714import { getSdk } from "../../utils/cache/getSdk" ;
15+ import { msSince } from "../../utils/date" ;
816import { logger } from "../../utils/logger" ;
17+ import {
18+ thirdwebClient ,
19+ toTransactionStatus ,
20+ toTransactionType ,
21+ } from "../../utils/sdk" ;
922import {
1023 ReportUsageParams ,
1124 UsageEventTxActionEnum ,
1225 reportUsage ,
1326} from "../../utils/usage" ;
1427import { WebhookData , sendWebhooks } from "../../utils/webhook" ;
1528
29+ const CANCEL_DEADLINE_MS = 1000 * 60 * 60 ; // 1 hour
30+
1631export const updateMinedUserOps = async ( ) => {
1732 try {
1833 const sendWebhookForQueueIds : WebhookData [ ] = [ ] ;
1934 const reportUsageForQueueIds : ReportUsageParams [ ] = [ ] ;
2035 await prisma . $transaction (
2136 async ( pgtx ) => {
2237 const userOps = await getSentUserOps ( { pgtx } ) ;
23-
2438 if ( userOps . length === 0 ) {
2539 return ;
2640 }
2741
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+ }
3852
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+ }
4579
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+ } ) ;
4990 }
50- const _sdk = await getSdk ( {
51- chainId : parseInt ( userOp . chainId ! ) ,
52- } ) ;
91+ return ;
92+ }
5393
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+ } ) ;
6899
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+ }
84114
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.
87125 await updateTx ( {
88126 pgtx,
89- queueId : userOp ! . id ,
127+ queueId : userOp . id ,
90128 data : {
91129 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 ( ) ,
101140 } ,
102141 } ) ;
103142
104143 logger ( {
105144 service : "worker" ,
106145 level : "info" ,
107- queueId : userOp ! . id ,
108- message : ` Updated with receipt` ,
146+ queueId : userOp . id ,
147+ message : " Updated with receipt" ,
109148 } ) ;
110149 sendWebhookForQueueIds . push ( {
111- queueId : userOp ! . id ,
150+ queueId : userOp . id ,
112151 status : TransactionStatus . Mined ,
113152 } ) ;
114153 reportUsageForQueueIds . push ( {
115154 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 ! ) ,
127165 } ,
128166 action : UsageEventTxActionEnum . MineTx ,
129167 } ) ;
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 ) ;
132180 } ,
133181 {
134- timeout : 5 * 60000 ,
182+ timeout : 5 * 60 * 1000 , // 5 minutes
135183 } ,
136184 ) ;
137185
@@ -141,9 +189,8 @@ export const updateMinedUserOps = async () => {
141189 logger ( {
142190 service : "worker" ,
143191 level : "error" ,
144- message : ` Failed to update receipts` ,
192+ message : " Failed to batch update receipts" ,
145193 error : err ,
146194 } ) ;
147- return ;
148195 }
149196} ;
0 commit comments