@@ -91,6 +91,8 @@ const SUPPORTED_TOKEN_ABIS = {
9191
9292const REVERTED_ERRORS = [ 'execution reverted' , 'insufficient funds for gas' ] ;
9393
94+ type BalanceTransactionMap = Map < SimulationToken , SimulationRequestTransaction > ;
95+
9496/**
9597 * Generate simulation data for a transaction.
9698 * @param request - The transaction to simulate.
@@ -199,7 +201,7 @@ function getNativeBalanceChange(
199201 * @param response - The simulation response.
200202 * @returns The parsed events.
201203 */
202- function getEvents ( response : SimulationResponse ) : ParsedEvent [ ] {
204+ export function getEvents ( response : SimulationResponse ) : ParsedEvent [ ] {
203205 /* istanbul ignore next */
204206 const logs = extractLogs (
205207 response . transactions [ 0 ] ?. callTrace ?? ( { } as SimulationResponseCallTrace ) ,
@@ -284,41 +286,45 @@ async function getTokenBalanceChanges(
284286 request : GetSimulationDataRequest ,
285287 events : ParsedEvent [ ] ,
286288) : Promise < SimulationTokenBalanceChange [ ] > {
287- const balanceTransactionsByToken = getTokenBalanceTransactions (
288- request ,
289- events ,
290- ) ;
289+ const balanceTxs = getTokenBalanceTransactions ( request , events ) ;
291290
292- const balanceTransactions = [ ...balanceTransactionsByToken . values ( ) ] ;
291+ log ( 'Generated balance transactions' , [ ...balanceTxs . after . values ( ) ] ) ;
293292
294- log ( 'Generated balance transactions' , balanceTransactions ) ;
293+ const transactions = [
294+ ...balanceTxs . before . values ( ) ,
295+ request ,
296+ ...balanceTxs . after . values ( ) ,
297+ ] ;
295298
296- if ( ! balanceTransactions . length ) {
299+ if ( transactions . length === 1 ) {
297300 return [ ] ;
298301 }
299302
300303 const response = await simulateTransactions ( request . chainId as Hex , {
301- transactions : [ ... balanceTransactions , request , ... balanceTransactions ] ,
304+ transactions,
302305 } ) ;
303306
304307 log ( 'Balance simulation response' , response ) ;
305308
306- if ( response . transactions . length !== balanceTransactions . length * 2 + 1 ) {
309+ if ( response . transactions . length !== transactions . length ) {
307310 throw new SimulationInvalidResponseError ( ) ;
308311 }
309312
310- return [ ...balanceTransactionsByToken . keys ( ) ]
313+ return [ ...balanceTxs . after . keys ( ) ]
311314 . map ( ( token , index ) => {
312- const previousBalance = getValueFromBalanceTransaction (
313- request . from ,
314- token ,
315- response . transactions [ index ] ,
316- ) ;
315+ const previousBalanceCheckSkipped = ! balanceTxs . before . get ( token ) ;
316+ const previousBalance = previousBalanceCheckSkipped
317+ ? '0x0'
318+ : getValueFromBalanceTransaction (
319+ request . from ,
320+ token ,
321+ response . transactions [ index ] ,
322+ ) ;
317323
318324 const newBalance = getValueFromBalanceTransaction (
319325 request . from ,
320326 token ,
321- response . transactions [ index + balanceTransactions . length + 1 ] ,
327+ response . transactions [ index + balanceTxs . before . size + 1 ] ,
322328 ) ;
323329
324330 const balanceChange = getSimulationBalanceChange (
@@ -347,8 +353,13 @@ async function getTokenBalanceChanges(
347353function getTokenBalanceTransactions (
348354 request : GetSimulationDataRequest ,
349355 events : ParsedEvent [ ] ,
350- ) : Map < SimulationToken , SimulationRequestTransaction > {
356+ ) : {
357+ before : BalanceTransactionMap ;
358+ after : BalanceTransactionMap ;
359+ } {
351360 const tokenKeys = new Set ( ) ;
361+ const before = new Map ( ) ;
362+ const after = new Map ( ) ;
352363
353364 const userEvents = events . filter (
354365 ( event ) =>
@@ -358,7 +369,7 @@ function getTokenBalanceTransactions(
358369
359370 log ( 'Filtered user events' , userEvents ) ;
360371
361- return userEvents . reduce ( ( result , event ) => {
372+ for ( const event of userEvents ) {
362373 const tokenIds = getEventTokenIds ( event ) ;
363374
364375 log ( 'Extracted token ids' , tokenIds ) ;
@@ -388,15 +399,37 @@ function getTokenBalanceTransactions(
388399 tokenId ,
389400 ) ;
390401
391- result . set ( simulationToken , {
402+ const transaction : SimulationRequestTransaction = {
392403 from : request . from ,
393404 to : event . contractAddress ,
394405 data,
395- } ) ;
406+ } ;
407+
408+ if ( skipPriorBalanceCheck ( event ) ) {
409+ after . set ( simulationToken , transaction ) ;
410+ } else {
411+ before . set ( simulationToken , transaction ) ;
412+ after . set ( simulationToken , transaction ) ;
413+ }
396414 }
415+ }
397416
398- return result ;
399- } , new Map < SimulationToken , SimulationRequestTransaction > ( ) ) ;
417+ return { before, after } ;
418+ }
419+
420+ /**
421+ * Check if an event needs to check the previous balance.
422+ * @param event - The parsed event.
423+ * @returns True if the prior balance check should be skipped.
424+ */
425+ function skipPriorBalanceCheck ( event : ParsedEvent ) : boolean {
426+ // In the case of an NFT mint, we cannot check the NFT owner before the mint
427+ // as the balance check transaction would revert.
428+ return (
429+ event . name === 'Transfer' &&
430+ event . tokenStandard === SimulationTokenStandard . erc721 &&
431+ parseInt ( event . args . from as string , 16 ) === 0
432+ ) ;
400433}
401434
402435/**
0 commit comments