@@ -206,80 +206,80 @@ fn generate_wallet_analysis_script(wallet: &str) -> String {
206206 (entries dedup_map)
207207 (lambda (entry) (get entry 1))))
208208
209- ;; Now aggregate by token mint
210- (define by_mint (group-by unique_transfers (lambda (tx) (get tx "mint"))))
209+ ;; Split all transfers by direction
210+ (define all_inflows (filter unique_transfers (lambda (t) (= (get t "transferType") "IN"))))
211+ (define all_outflows (filter unique_transfers (lambda (t) (= (get t "transferType") "OUT"))))
211212
212- ;; For each token, aggregate senders/receivers
213- (define token_summaries
214- (map
215- (entries by_mint)
216- (lambda (mint_pair)
213+ ;; Count unique tokens
214+ (define unique_tokens (group-by unique_transfers (lambda (tx) (get tx "mint"))))
215+ (define num_tokens (length (entries unique_tokens)))
216+
217+ ;; Aggregate ALL inflows by sender (with token details)
218+ (define global_senders
219+ (reduce
220+ all_inflows
221+ {{}}
222+ (lambda (acc tx)
217223 (do
218- (define mint (get mint_pair 0))
219- (define txs (get mint_pair 1))
220-
221- ;; Get token symbol from first tx
222- (define symbol (if (> (length txs) 0)
223- (get (get txs 0) "tokenSymbol")
224- mint))
225-
226- ;; Split by direction
227- (define inflows (filter txs (lambda (t) (= (get t "transferType") "IN"))))
228- (define outflows (filter txs (lambda (t) (= (get t "transferType") "OUT"))))
229-
230- ;; Aggregate inflows by sender
231- (define inflow_agg
232- (reduce
233- inflows
234- {{}}
235- (lambda (acc tx)
236- (do
237- (define from (get tx "from"))
238- (define amt (float (get tx "tokenAmount")))
239- (define existing (get acc from))
240- (define current (if existing existing 0))
241- (put acc from (+ current amt))))))
242-
243- ;; Aggregate outflows by receiver
244- (define outflow_agg
245- (reduce
246- outflows
247- {{}}
248- (lambda (acc tx)
249- (do
250- (define to (get tx "to"))
251- (define amt (float (get tx "tokenAmount")))
252- (define existing (get acc to))
253- (define current (if existing existing 0))
254- (put acc to (+ current amt))))))
255-
256- ;; Sort and take top 5
257- (define top_senders
258- (take 5
259- (sort
260- (entries inflow_agg)
261- (lambda (a b) (> (get a 1) (get b 1))))))
262-
263- (define top_receivers
264- (take 5
265- (sort
266- (entries outflow_agg)
267- (lambda (a b) (> (get a 1) (get b 1))))))
268-
269- {{:mint mint
270- :symbol symbol
271- :transfer_count (length txs)
272- :inflow_count (length inflows)
273- :outflow_count (length outflows)
274- :top_senders top_senders
275- :top_receivers top_receivers}})))
276-
277- ;; Return AGGREGATED summaries (NOT raw transfers!)
224+ (define from (get tx "from"))
225+ (define symbol (get tx "tokenSymbol"))
226+ (define amt (float (get tx "tokenAmount")))
227+
228+ ;; Get or create sender record
229+ (define existing (get acc from))
230+ (define sender_record (if existing existing {{}}))
231+
232+ ;; Add token amount to sender's token list
233+ (define token_existing (get sender_record symbol))
234+ (define token_current (if token_existing token_existing 0))
235+ (define updated_record (put sender_record symbol (+ token_current amt)))
236+
237+ (put acc from updated_record)))))
238+
239+ ;; Aggregate ALL outflows by receiver (with token details)
240+ (define global_receivers
241+ (reduce
242+ all_outflows
243+ {{}}
244+ (lambda (acc tx)
245+ (do
246+ (define to (get tx "to"))
247+ (define symbol (get tx "tokenSymbol"))
248+ (define amt (float (get tx "tokenAmount")))
249+
250+ ;; Get or create receiver record
251+ (define existing (get acc to))
252+ (define receiver_record (if existing existing {{}}))
253+
254+ ;; Add token amount to receiver's token list
255+ (define token_existing (get receiver_record symbol))
256+ (define token_current (if token_existing token_existing 0))
257+ (define updated_record (put receiver_record symbol (+ token_current amt)))
258+
259+ (put acc to updated_record)))))
260+
261+ ;; Convert to sorted arrays and take top 3
262+ (define top_senders
263+ (take 3
264+ (sort
265+ (entries global_senders)
266+ (lambda (a b) (> (length (entries (get a 1))) (length (entries (get b 1))))))))
267+
268+ (define top_receivers
269+ (take 3
270+ (sort
271+ (entries global_receivers)
272+ (lambda (a b) (> (length (entries (get a 1))) (length (entries (get b 1))))))))
273+
274+ ;; Return MINIMAL summary (no per-token details!)
278275 {{:wallet target
279276 :total_transfers_raw (length all_transfers)
280277 :total_transfers_unique (length unique_transfers)
281- :num_tokens (length token_summaries)
282- :tokens token_summaries}})
278+ :num_tokens num_tokens
279+ :inflow_count (length all_inflows)
280+ :outflow_count (length all_outflows)
281+ :top_senders top_senders
282+ :top_receivers top_receivers}})
283283"# , wallet)
284284}
285285
@@ -305,90 +305,81 @@ fn extract_summary_json(result: &ovsm::Value) -> Result<String> {
305305 . and_then ( |v| v. as_int ( ) . ok ( ) )
306306 . unwrap_or ( 0 ) ;
307307
308- let tokens_array = obj. get ( "tokens" )
309- . ok_or_else ( || anyhow:: anyhow!( "Expected tokens key" ) ) ?
308+ let inflow_count = obj. get ( "inflow_count" )
309+ . and_then ( |v| v. as_int ( ) . ok ( ) )
310+ . unwrap_or ( 0 ) ;
311+
312+ let outflow_count = obj. get ( "outflow_count" )
313+ . and_then ( |v| v. as_int ( ) . ok ( ) )
314+ . unwrap_or ( 0 ) ;
315+
316+ // Extract top senders: [[address, {symbol: amount, ...}], ...]
317+ let top_senders_array = obj. get ( "top_senders" )
318+ . ok_or_else ( || anyhow:: anyhow!( "Expected top_senders key" ) ) ?
319+ . as_array ( ) ?;
320+
321+ let mut top_senders = Vec :: new ( ) ;
322+ for sender_pair in top_senders_array {
323+ let pair = sender_pair. as_array ( ) ?;
324+ if pair. len ( ) == 2 {
325+ let address = pair[ 0 ] . as_string ( ) . ok ( ) . unwrap_or ( "unknown" ) ;
326+ let tokens_obj = pair[ 1 ] . as_object ( ) ?;
327+
328+ let mut tokens = Vec :: new ( ) ;
329+ for ( symbol, amount) in tokens_obj. iter ( ) {
330+ if let Ok ( amt) = amount. as_float ( ) {
331+ tokens. push ( json ! ( {
332+ "symbol" : symbol,
333+ "amount" : amt
334+ } ) ) ;
335+ }
336+ }
337+
338+ top_senders. push ( json ! ( {
339+ "address" : address,
340+ "tokens" : tokens
341+ } ) ) ;
342+ }
343+ }
344+
345+ // Extract top receivers: [[address, {symbol: amount, ...}], ...]
346+ let top_receivers_array = obj. get ( "top_receivers" )
347+ . ok_or_else ( || anyhow:: anyhow!( "Expected top_receivers key" ) ) ?
310348 . as_array ( ) ?;
311349
312- let mut tokens_summary = Vec :: new ( ) ;
313-
314- for token in tokens_array {
315- let token_obj = token. as_object ( ) ?;
316-
317- let symbol = token_obj. get ( "symbol" )
318- . and_then ( |v| v. as_string ( ) . ok ( ) )
319- . unwrap_or ( "unknown" ) ;
320-
321- let mint = token_obj. get ( "mint" )
322- . and_then ( |v| v. as_string ( ) . ok ( ) )
323- . unwrap_or ( "unknown" ) ;
324-
325- let transfer_count = token_obj. get ( "transfer_count" )
326- . and_then ( |v| v. as_int ( ) . ok ( ) )
327- . unwrap_or ( 0 ) ;
328-
329- let inflow_count = token_obj. get ( "inflow_count" )
330- . and_then ( |v| v. as_int ( ) . ok ( ) )
331- . unwrap_or ( 0 ) ;
332-
333- let outflow_count = token_obj. get ( "outflow_count" )
334- . and_then ( |v| v. as_int ( ) . ok ( ) )
335- . unwrap_or ( 0 ) ;
336-
337- // Extract top senders (address, amount pairs)
338- let top_senders = token_obj. get ( "top_senders" )
339- . and_then ( |v| v. as_array ( ) . ok ( ) )
340- . map ( |arr| {
341- arr. iter ( ) . filter_map ( |pair| {
342- pair. as_array ( ) . ok ( ) . and_then ( |p| {
343- if p. len ( ) == 2 {
344- Some ( json ! ( {
345- "address" : p[ 0 ] . as_string( ) . ok( ) . unwrap_or( "unknown" ) ,
346- "amount" : p[ 1 ] . as_float( ) . ok( ) . unwrap_or( 0.0 )
347- } ) )
348- } else {
349- None
350- }
351- } )
352- } ) . collect :: < Vec < _ > > ( )
353- } )
354- . unwrap_or_default ( ) ;
355-
356- // Extract top receivers
357- let top_receivers = token_obj. get ( "top_receivers" )
358- . and_then ( |v| v. as_array ( ) . ok ( ) )
359- . map ( |arr| {
360- arr. iter ( ) . filter_map ( |pair| {
361- pair. as_array ( ) . ok ( ) . and_then ( |p| {
362- if p. len ( ) == 2 {
363- Some ( json ! ( {
364- "address" : p[ 0 ] . as_string( ) . ok( ) . unwrap_or( "unknown" ) ,
365- "amount" : p[ 1 ] . as_float( ) . ok( ) . unwrap_or( 0.0 )
366- } ) )
367- } else {
368- None
369- }
370- } )
371- } ) . collect :: < Vec < _ > > ( )
372- } )
373- . unwrap_or_default ( ) ;
374-
375- tokens_summary. push ( json ! ( {
376- "symbol" : symbol,
377- "mint" : mint,
378- "transfer_count" : transfer_count,
379- "inflow_count" : inflow_count,
380- "outflow_count" : outflow_count,
381- "top_senders" : top_senders,
382- "top_receivers" : top_receivers
383- } ) ) ;
350+ let mut top_receivers = Vec :: new ( ) ;
351+ for receiver_pair in top_receivers_array {
352+ let pair = receiver_pair. as_array ( ) ?;
353+ if pair. len ( ) == 2 {
354+ let address = pair[ 0 ] . as_string ( ) . ok ( ) . unwrap_or ( "unknown" ) ;
355+ let tokens_obj = pair[ 1 ] . as_object ( ) ?;
356+
357+ let mut tokens = Vec :: new ( ) ;
358+ for ( symbol, amount) in tokens_obj. iter ( ) {
359+ if let Ok ( amt) = amount. as_float ( ) {
360+ tokens. push ( json ! ( {
361+ "symbol" : symbol,
362+ "amount" : amt
363+ } ) ) ;
364+ }
365+ }
366+
367+ top_receivers. push ( json ! ( {
368+ "address" : address,
369+ "tokens" : tokens
370+ } ) ) ;
371+ }
384372 }
385373
386374 let summary = json ! ( {
387375 "wallet" : wallet,
388376 "total_transfers_raw" : total_raw,
389377 "total_transfers_unique" : total_unique,
390378 "num_tokens" : num_tokens,
391- "tokens" : tokens_summary
379+ "inflow_count" : inflow_count,
380+ "outflow_count" : outflow_count,
381+ "top_senders" : top_senders,
382+ "top_receivers" : top_receivers
392383 } ) ;
393384
394385 Ok ( serde_json:: to_string_pretty ( & summary) ?)
@@ -399,28 +390,23 @@ async fn format_wallet_analysis(ai_service: &mut AiService, wallet: &str, summar
399390 // System prompt for custom formatting (bypass planning mode)
400391 let system_prompt = r#"You are a markdown formatter. The blockchain analysis is ALREADY COMPLETE.
401392
402- Your ONLY job: Convert the provided JSON into clean markdown tables .
393+ Your ONLY job: Convert the provided JSON into clean markdown.
403394
404395DO NOT analyze, interpret, or add commentary. ONLY format what's given:
405396
406- For each token in the JSON, create:
407-
408- **{symbol} ({mint})**
409- - Transfers: {transfer_count} ({inflow_count} in, {outflow_count} out)
410-
411- Top Inflows:
412- | Address | Amount |
413- |---------|--------|
414- [list top_senders array]
397+ **Wallet Summary**
398+ - Total: {total_transfers_unique} unique transfers ({inflow_count} in, {outflow_count} out)
399+ - Tokens: {num_tokens} different tokens
415400
416- Top Outflows:
417- | Address | Amount |
418- |---------|--------|
419- [list top_receivers array]
401+ ** Top 3 Senders** (addresses sending TO this wallet)
402+ | Address | Tokens Sent |
403+ |---------|------------- |
404+ [For each sender in top_senders array, list address and all tokens with amounts ]
420405
421- Summary at top:
422- - Total: {total_transfers_unique} unique transfers
423- - Tokens: {num_tokens}
406+ **Top 3 Receivers** (addresses receiving FROM this wallet)
407+ | Address | Tokens Received |
408+ |---------|-----------------|
409+ [For each receiver in top_receivers array, list address and all tokens with amounts]
424410
425411NO analysis. NO interpretation. ONLY formatting the JSON into tables."# . to_string ( ) ;
426412
0 commit comments