fix(sui): fix duplicate operations at page boundary in AlpacaApi.listOperations#15064
fix(sui): fix duplicate operations at page boundary in AlpacaApi.listOperations#15064jnicoulaud-ledger wants to merge 6 commits intodevelopfrom
AlpacaApi.listOperations#15064Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
3431501 to
1e6245c
Compare
|
There was a problem hiding this comment.
Pull request overview
Fixes Sui AlpacaApi.listOperations pagination so the same operation isn’t returned on multiple pages (notably at page boundaries when merging IN/OUT streams).
Changes:
- Reworks
getListOperationspagination by merging IN/OUT transactions, de-duping by digest, sorting by timestamp, filtering by a new cursor boundary, and emitting a new cursor format. - Replaces the old
dedupOperationsapproach with new cursor parsing/serialization and adds extensive unit tests for cursor/boundary scenarios. - Adds an integration test asserting that fully paginated
ascresults equal the reverse of fully paginateddescresults.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
libs/coin-modules/coin-sui/src/network/sdk.ts |
Implements new merged pagination logic and introduces a timestamp+digest cursor format. |
libs/coin-modules/coin-sui/src/network/sdk.test.ts |
Adds unit tests for cursor parsing and page-boundary/non-duplication behavior; removes old dedupOperations tests. |
libs/coin-modules/coin-sui/src/api/index.integ.test.ts |
Adds a live integration test comparing full asc vs reversed desc pagination results. |
.changeset/few-olives-agree.md |
Declares a release bump for @ledgerhq/coin-sui for this pagination change. |
| if (op.digest === parsedCursor.digest) return false; | ||
| if (parsedCursor.timestamp === undefined) return true; | ||
|
|
||
| const ts = Number(op.timestampMs ?? 0); | ||
| return order === "asc" ? ts > parsedCursor.timestamp : ts < parsedCursor.timestamp; |
There was a problem hiding this comment.
The boundary filter drops all operations with timestampMs === parsedCursor.timestamp (except it also drops the boundary digest explicitly). If multiple distinct operations share the boundary timestamp, they will never be returned on subsequent pages. To avoid missing ops, filter using a total ordering consistent with the sort and cursor (e.g., for asc: keep (ts > cursor.ts) OR (ts === cursor.ts AND digest > cursor.digest); similarly for desc).
| if (op.digest === parsedCursor.digest) return false; | |
| if (parsedCursor.timestamp === undefined) return true; | |
| const ts = Number(op.timestampMs ?? 0); | |
| return order === "asc" ? ts > parsedCursor.timestamp : ts < parsedCursor.timestamp; | |
| // never return the boundary operation itself | |
| if (op.digest === parsedCursor.digest) return false; | |
| const boundaryTs = parsedCursor.timestamp; | |
| const boundaryDigest = parsedCursor.digest; | |
| // if we don't have full boundary information, fall back to previous behavior | |
| if (boundaryTs === undefined || !boundaryDigest) return true; | |
| const ts = Number(op.timestampMs ?? 0); | |
| if (order === "asc") { | |
| // keep ops strictly after the cursor in (timestamp, digest) ordering | |
| return ts > boundaryTs || (ts === boundaryTs && op.digest > boundaryDigest); | |
| } | |
| // order === "desc": keep ops strictly after the cursor in reverse (timestamp, digest) ordering | |
| return ts < boundaryTs || (ts === boundaryTs && op.digest < boundaryDigest); |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| if (op.digest === parsedCursor.digest) return false; | ||
| if (parsedCursor.timestamp === undefined) return true; | ||
|
|
||
| const ts = Number(op.timestampMs ?? 0); | ||
| return order === "asc" ? ts > parsedCursor.timestamp : ts < parsedCursor.timestamp; |
There was a problem hiding this comment.
The cursor filtering only compares timestamps (ts > parsedCursor.timestamp / <) and removes the exact digest match. If multiple transactions share the same timestampMs (common for txs in the same checkpoint), this will drop all ops at that timestamp on subsequent pages (except possibly the first page), causing missing operations. The filtering should be consistent with the full sort order (timestamp + digest tie-breaker), e.g. when timestamps are equal include only digests that come after/before the cursor digest depending on order.
| if (op.digest === parsedCursor.digest) return false; | |
| if (parsedCursor.timestamp === undefined) return true; | |
| const ts = Number(op.timestampMs ?? 0); | |
| return order === "asc" ? ts > parsedCursor.timestamp : ts < parsedCursor.timestamp; | |
| // never include the operation exactly at the cursor | |
| if (op.digest === parsedCursor.digest) return false; | |
| const cursorTs = parsedCursor.timestamp; | |
| if (cursorTs === undefined) return true; | |
| const ts = Number(op.timestampMs ?? 0); | |
| if (ts === cursorTs && parsedCursor.digest) { | |
| // when timestamps are equal, use digest as tie-breaker to match sort order | |
| return order === "asc" | |
| ? op.digest > parsedCursor.digest | |
| : op.digest < parsedCursor.digest; | |
| } | |
| return order === "asc" ? ts > cursorTs : ts < cursorTs; |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>


✅ Checklist
npx changesetwas attached.📝 Description
Pagination returns same operation in different pages.
❓ Context
🧐 Checklist for the PR Reviewers