@@ -528,11 +528,49 @@ export class PgStoreV2 extends BasePgStoreModule {
528528 }
529529
530530 async getAddressTransactions (
531- args : AddressParams & TransactionPaginationQueryParams
532- ) : Promise < DbPaginatedResult < DbTxWithAddressTransfers > > {
531+ args : AddressParams & TransactionPaginationQueryParams & { cursor ?: string }
532+ ) : Promise < DbCursorPaginatedResult < DbTxWithAddressTransfers > > {
533533 return await this . sqlTransaction ( async sql => {
534534 const limit = args . limit ?? TransactionLimitParamSchema . default ;
535535 const offset = args . offset ?? 0 ;
536+
537+ // Parse cursor if provided (format: "indexBlockHash:microblockSequence:txIndex")
538+ let cursorFilter = sql `` ;
539+ if ( args . cursor ) {
540+ const parts = args . cursor . split ( ':' ) ;
541+ if ( parts . length !== 3 ) {
542+ throw new InvalidRequestError (
543+ 'Invalid cursor format' ,
544+ InvalidRequestErrorType . invalid_param
545+ ) ;
546+ }
547+ const [ indexBlockHash , microblockSequenceStr , txIndexStr ] = parts ;
548+ const microblockSequence = parseInt ( microblockSequenceStr , 10 ) ;
549+ const txIndex = parseInt ( txIndexStr , 10 ) ;
550+ if ( ! indexBlockHash || isNaN ( microblockSequence ) || isNaN ( txIndex ) ) {
551+ throw new InvalidRequestError (
552+ 'Invalid cursor format' ,
553+ InvalidRequestErrorType . invalid_param
554+ ) ;
555+ }
556+ // Look up block_height from index_block_hash for the row-value comparison
557+ const blockHeightQuery = await sql < { block_height : number } [ ] > `
558+ SELECT block_height FROM blocks WHERE index_block_hash = ${ indexBlockHash } LIMIT 1
559+ ` ;
560+ if ( blockHeightQuery . length === 0 ) {
561+ throw new InvalidRequestError (
562+ 'Invalid cursor: block not found' ,
563+ InvalidRequestErrorType . invalid_param
564+ ) ;
565+ }
566+ const blockHeight = blockHeightQuery [ 0 ] . block_height ;
567+
568+ cursorFilter = sql `
569+ AND (p.block_height, p.microblock_sequence, p.tx_index)
570+ <= (${ blockHeight } , ${ microblockSequence } , ${ txIndex } )
571+ ` ;
572+ }
573+
536574 const resultQuery = await sql < ( AddressTransfersTxQueryResult & { count : number } ) [ ] > `
537575 SELECT
538576 ${ sql ( prefixedCols ( TX_COLUMNS , 't' ) ) } ,
@@ -555,17 +593,68 @@ export class PgStoreV2 extends BasePgStoreModule {
555593 WHERE p.principal = ${ args . address }
556594 AND p.canonical = TRUE
557595 AND p.microblock_canonical = TRUE
596+ ${ cursorFilter }
558597 ORDER BY p.block_height DESC, p.microblock_sequence DESC, p.tx_index DESC
559- LIMIT ${ limit }
560- OFFSET ${ offset }
598+ LIMIT ${ limit + 1 }
561599 ` ;
562- const total = resultQuery . length > 0 ? resultQuery [ 0 ] . count : 0 ;
563- const parsed = resultQuery . map ( r => parseAccountTransferSummaryTxQueryResult ( r ) ) ;
600+
601+ const hasNextPage = resultQuery . count > limit ;
602+ const results = hasNextPage ? resultQuery . slice ( 0 , limit ) : resultQuery ;
603+
604+ const total = resultQuery . count > 0 ? resultQuery [ 0 ] . count : 0 ;
605+ const parsed = results . map ( r => parseAccountTransferSummaryTxQueryResult ( r ) ) ;
606+
607+ // Generate prev cursor from the last result
608+ const lastResult = resultQuery [ resultQuery . length - 1 ] ;
609+ const prevCursor =
610+ hasNextPage && lastResult
611+ ? `${ lastResult . index_block_hash } :${ lastResult . microblock_sequence } :${ lastResult . tx_index } `
612+ : null ;
613+
614+ // Generate current cursor from first result
615+ const firstResult = results [ 0 ] ;
616+ const currentCursor = firstResult
617+ ? `${ firstResult . index_block_hash } :${ firstResult . microblock_sequence } :${ firstResult . tx_index } `
618+ : null ;
619+
620+ // Generate next cursor by looking for the first item of the previous page
621+ let nextCursor : string | null = null ;
622+ if ( firstResult ) {
623+ // Find the item that would start the previous page
624+ // We look for items "before" our current first result (greater in DESC order)
625+ // and skip (limit - 1) to find the start of that page
626+ const prevQuery = await sql <
627+ { index_block_hash : string ; microblock_sequence : number ; tx_index : number } [ ]
628+ > `
629+ SELECT p.index_block_hash, p.microblock_sequence, p.tx_index
630+ FROM principal_txs AS p
631+ WHERE p.principal = ${ args . address }
632+ AND p.canonical = TRUE
633+ AND p.microblock_canonical = TRUE
634+ AND (p.block_height, p.microblock_sequence, p.tx_index)
635+ > (
636+ ${ firstResult . block_height } ,
637+ ${ firstResult . microblock_sequence } ,
638+ ${ firstResult . tx_index }
639+ )
640+ ORDER BY p.block_height ASC, p.microblock_sequence ASC, p.tx_index ASC
641+ OFFSET ${ limit - 1 }
642+ LIMIT 1
643+ ` ;
644+ if ( prevQuery . length > 0 ) {
645+ const prev = prevQuery [ 0 ] ;
646+ nextCursor = `${ prev . index_block_hash } :${ prev . microblock_sequence } :${ prev . tx_index } ` ;
647+ }
648+ }
649+
564650 return {
565651 total,
566652 limit,
567653 offset,
568654 results : parsed ,
655+ next_cursor : nextCursor ,
656+ prev_cursor : prevCursor ,
657+ current_cursor : currentCursor ,
569658 } ;
570659 } ) ;
571660 }
0 commit comments