@@ -332,17 +332,27 @@ export class SorobanEventWorker {
332332 } ,
333333 } ) ;
334334
335- await tx . streamEvent . create ( {
336- data : {
337- streamId,
338- eventType : 'CREATED' ,
339- amount : depositedAmount ,
340- transactionHash : event . txHash ,
341- ledgerSequence : event . ledger ,
342- timestamp : startTime ,
343- metadata : JSON . stringify ( { tokenAddress, ratePerSecond } ) ,
344- } ,
335+ const existingEvent = await tx . streamEvent . findUnique ( {
336+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'CREATED' } } ,
337+ select : { id : true } ,
345338 } ) ;
339+ if ( existingEvent ) {
340+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=CREATED` ) ;
341+ } else {
342+ await tx . streamEvent . upsert ( {
343+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'CREATED' } } ,
344+ create : {
345+ streamId,
346+ eventType : 'CREATED' ,
347+ amount : depositedAmount ,
348+ transactionHash : event . txHash ,
349+ ledgerSequence : event . ledger ,
350+ timestamp : startTime ,
351+ metadata : JSON . stringify ( { tokenAddress, ratePerSecond } ) ,
352+ } ,
353+ update : { } ,
354+ } ) ;
355+ }
346356 } ) ;
347357
348358 sseService . broadcastToStream ( String ( streamId ) , 'stream.created' , {
@@ -382,17 +392,27 @@ export class SorobanEventWorker {
382392 } ,
383393 } ) ;
384394
385- await tx . streamEvent . create ( {
386- data : {
387- streamId,
388- eventType : 'TOPPED_UP' ,
389- amount,
390- transactionHash : event . txHash ,
391- ledgerSequence : event . ledger ,
392- timestamp,
393- metadata : JSON . stringify ( { newDepositedAmount } ) ,
394- } ,
395+ const existingEvent = await tx . streamEvent . findUnique ( {
396+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'TOPPED_UP' } } ,
397+ select : { id : true } ,
395398 } ) ;
399+ if ( existingEvent ) {
400+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=TOPPED_UP` ) ;
401+ } else {
402+ await tx . streamEvent . upsert ( {
403+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'TOPPED_UP' } } ,
404+ create : {
405+ streamId,
406+ eventType : 'TOPPED_UP' ,
407+ amount,
408+ transactionHash : event . txHash ,
409+ ledgerSequence : event . ledger ,
410+ timestamp,
411+ metadata : JSON . stringify ( { newDepositedAmount } ) ,
412+ } ,
413+ update : { } ,
414+ } ) ;
415+ }
396416 } ) ;
397417
398418 sseService . broadcastToStream ( String ( streamId ) , 'stream.topped_up' , {
@@ -438,17 +458,27 @@ export class SorobanEventWorker {
438458 } ,
439459 } ) ;
440460
441- await tx . streamEvent . create ( {
442- data : {
443- streamId,
444- eventType : 'WITHDRAWN' ,
445- amount,
446- transactionHash : event . txHash ,
447- ledgerSequence : event . ledger ,
448- timestamp,
449- metadata : JSON . stringify ( { recipient } ) ,
450- } ,
461+ const existingEvent = await tx . streamEvent . findUnique ( {
462+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'WITHDRAWN' } } ,
463+ select : { id : true } ,
451464 } ) ;
465+ if ( existingEvent ) {
466+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=WITHDRAWN` ) ;
467+ } else {
468+ await tx . streamEvent . upsert ( {
469+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'WITHDRAWN' } } ,
470+ create : {
471+ streamId,
472+ eventType : 'WITHDRAWN' ,
473+ amount,
474+ transactionHash : event . txHash ,
475+ ledgerSequence : event . ledger ,
476+ timestamp,
477+ metadata : JSON . stringify ( { recipient } ) ,
478+ } ,
479+ update : { } ,
480+ } ) ;
481+ }
452482 } ) ;
453483
454484 sseService . broadcastToStream ( String ( streamId ) , 'stream.withdrawn' , {
@@ -486,17 +516,27 @@ export class SorobanEventWorker {
486516 } ,
487517 } ) ;
488518
489- await tx . streamEvent . create ( {
490- data : {
491- streamId,
492- eventType : 'CANCELLED' ,
493- amount : refundedAmount ,
494- transactionHash : event . txHash ,
495- ledgerSequence : event . ledger ,
496- timestamp,
497- metadata : JSON . stringify ( { amountWithdrawn, refundedAmount } ) ,
498- } ,
519+ const existingEvent = await tx . streamEvent . findUnique ( {
520+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'CANCELLED' } } ,
521+ select : { id : true } ,
499522 } ) ;
523+ if ( existingEvent ) {
524+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=CANCELLED` ) ;
525+ } else {
526+ await tx . streamEvent . upsert ( {
527+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'CANCELLED' } } ,
528+ create : {
529+ streamId,
530+ eventType : 'CANCELLED' ,
531+ amount : refundedAmount ,
532+ transactionHash : event . txHash ,
533+ ledgerSequence : event . ledger ,
534+ timestamp,
535+ metadata : JSON . stringify ( { amountWithdrawn, refundedAmount } ) ,
536+ } ,
537+ update : { } ,
538+ } ) ;
539+ }
500540 } ) ;
501541
502542 sseService . broadcastToStream ( String ( streamId ) , 'stream.cancelled' , {
@@ -534,17 +574,27 @@ export class SorobanEventWorker {
534574 } ,
535575 } ) ;
536576
537- await tx . streamEvent . create ( {
538- data : {
539- streamId,
540- eventType : 'COMPLETED' ,
541- amount : totalWithdrawn ,
542- transactionHash : event . txHash ,
543- ledgerSequence : event . ledger ,
544- timestamp,
545- metadata : JSON . stringify ( { recipient } ) ,
546- } ,
577+ const existingEvent = await tx . streamEvent . findUnique ( {
578+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'COMPLETED' } } ,
579+ select : { id : true } ,
547580 } ) ;
581+ if ( existingEvent ) {
582+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=COMPLETED` ) ;
583+ } else {
584+ await tx . streamEvent . upsert ( {
585+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'COMPLETED' } } ,
586+ create : {
587+ streamId,
588+ eventType : 'COMPLETED' ,
589+ amount : totalWithdrawn ,
590+ transactionHash : event . txHash ,
591+ ledgerSequence : event . ledger ,
592+ timestamp,
593+ metadata : JSON . stringify ( { recipient } ) ,
594+ } ,
595+ update : { } ,
596+ } ) ;
597+ }
548598 } ) ;
549599
550600 sseService . broadcastToStream ( String ( streamId ) , 'stream.completed' , {
@@ -573,17 +623,27 @@ export class SorobanEventWorker {
573623 const token = decodeAddress ( body [ 'token' ] ) ;
574624 const timestamp = Math . floor ( Date . now ( ) / 1000 ) ;
575625
576- await prisma . streamEvent . create ( {
577- data : {
578- streamId,
579- eventType : 'FEE_COLLECTED' ,
580- amount : feeAmount ,
581- transactionHash : event . txHash ,
582- ledgerSequence : event . ledger ,
583- timestamp,
584- metadata : JSON . stringify ( { treasury, token } ) ,
585- } ,
626+ const existingEvent = await prisma . streamEvent . findUnique ( {
627+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'FEE_COLLECTED' } } ,
628+ select : { id : true } ,
586629 } ) ;
630+ if ( existingEvent ) {
631+ logger . warn ( `[SorobanWorker] Duplicate StreamEvent skipped: txHash=${ event . txHash } type=FEE_COLLECTED` ) ;
632+ } else {
633+ await prisma . streamEvent . upsert ( {
634+ where : { transactionHash_eventType : { transactionHash : event . txHash , eventType : 'FEE_COLLECTED' } } ,
635+ create : {
636+ streamId,
637+ eventType : 'FEE_COLLECTED' ,
638+ amount : feeAmount ,
639+ transactionHash : event . txHash ,
640+ ledgerSequence : event . ledger ,
641+ timestamp,
642+ metadata : JSON . stringify ( { treasury, token } ) ,
643+ } ,
644+ update : { } ,
645+ } ) ;
646+ }
587647
588648 // Broadcast to admin channel for treasury reporting
589649 sseService . broadcast ( 'admin' , 'stream.fee_collected' , {
0 commit comments