Skip to content

Commit b9f95b2

Browse files
committed
feat(research): Add pagination with deduplication and infinite loop protection
- Implement cursor-based pagination with up to 100 iterations - Add duplicate cursor detection to prevent infinite loops - Deduplicate transfers by txId + from + to (same tx can have multiple token transfers) - Show both total_transfers_raw and total_transfers_unique in output - MCP server hardcodes 50 transfers per page, ignores :limit parameter - Pagination properly fetches multiple pages and stops when cursor repeats This ensures we fetch ALL wallet transfers, not just the first 50.
1 parent 5ddff71 commit b9f95b2

File tree

1 file changed

+36
-7
lines changed

1 file changed

+36
-7
lines changed

src/commands/research.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,14 @@ fn generate_wallet_analysis_script(wallet: &str) -> String {
158158
(define all_transfers [])
159159
(define next_cursor null)
160160
(define is_first_page true)
161+
(define prev_cursor null)
162+
(define max_iterations 100)
163+
(define iteration_count 0)
161164
162-
(while (or is_first_page next_cursor)
165+
(while (and (or is_first_page next_cursor) (< iteration_count max_iterations))
163166
(do
167+
(set! iteration_count (+ iteration_count 1))
168+
164169
;; Build request params based on whether we have a cursor
165170
(define params
166171
(if is_first_page
@@ -170,16 +175,39 @@ fn generate_wallet_analysis_script(wallet: &str) -> String {
170175
;; Fetch batch
171176
(define resp (get_account_transfers params))
172177
(define batch (get resp "data"))
178+
(define new_cursor (get resp "nextPageSignature"))
179+
180+
;; Append data if we got any
181+
(if (> (length batch) 0)
182+
(set! all_transfers (append all_transfers batch))
183+
null)
173184
174-
;; Append to results
175-
(set! all_transfers (append all_transfers batch))
185+
;; Stop if cursor hasn't changed (prevents infinite loop with same cursor)
186+
(if (and (not is_first_page) (= new_cursor prev_cursor))
187+
(set! next_cursor null)
188+
(do
189+
(set! prev_cursor next_cursor)
190+
(set! next_cursor new_cursor)))
176191
177-
;; Get next cursor for pagination
178-
(set! next_cursor (get resp "nextPageSignature"))
179192
(set! is_first_page false)))
180193
194+
;; Deduplicate transfers by transaction ID + from + to (same tx can have multiple token transfers)
195+
(define dedup_map
196+
(reduce
197+
all_transfers
198+
{{}}
199+
(lambda (acc tx)
200+
(do
201+
(define key (+ (get tx "txId") "_" (get tx "from") "_" (get tx "to")))
202+
(put acc key tx)))))
203+
204+
(define unique_transfers
205+
(map
206+
(entries dedup_map)
207+
(lambda (entry) (get entry 1))))
208+
181209
;; Now aggregate by token mint
182-
(define by_mint (group-by all_transfers (lambda (tx) (get tx "mint"))))
210+
(define by_mint (group-by unique_transfers (lambda (tx) (get tx "mint"))))
183211
184212
;; For each token, aggregate senders/receivers
185213
(define token_summaries
@@ -248,7 +276,8 @@ fn generate_wallet_analysis_script(wallet: &str) -> String {
248276
249277
;; Return AGGREGATED summaries (NOT raw transfers!)
250278
{{:wallet target
251-
:total_transfers (length all_transfers)
279+
:total_transfers_raw (length all_transfers)
280+
:total_transfers_unique (length unique_transfers)
252281
:num_tokens (length token_summaries)
253282
:tokens token_summaries}})
254283
"#, wallet)

0 commit comments

Comments
 (0)