|
1 | | -;;; Stream Pump.fun Events - PRODUCTION FORENSICS TOOL v2 |
| 1 | +;;; Stream Pump.fun Events - PRODUCTION FORENSICS TOOL v3 |
2 | 2 | ;;; |
3 | | -;;; Clean, professional output for blockchain investigation |
| 3 | +;;; REWRITTEN: Now uses TokenTransfer events (not logs!) |
4 | 4 | ;;; - Full addresses (no truncation) |
5 | | -;;; - Token mints and amounts |
| 5 | +;;; - Actual token mints from transfer events |
| 6 | +;;; - Real token amounts (not parsed from binary) |
6 | 7 | ;;; - Clean single-line output format |
7 | 8 | ;;; |
8 | 9 | ;;; Usage: |
9 | 10 | ;;; 1. Start: osvm stream --programs pumpfun --port 8080 |
10 | | -;;; 2. Run: osvm ovsm run stream_pumpfun_v2.ovsm |
| 11 | +;;; 2. Run: osvm ovsm run stream_pumpfun.ovsm |
11 | 12 |
|
12 | 13 | (define stream-id (stream-connect "http://localhost:8080" :programs ["pumpfun"])) |
13 | 14 |
|
14 | 15 | (println "") |
15 | 16 | (println "═══════════════════════════════════════════════════════════════") |
16 | | -(println " OSVM PUMP.FUN FORENSICS MONITOR") |
17 | | -(println " Connected to event stream - monitoring transactions...") |
| 17 | +(println " OSVM PUMP.FUN FORENSICS MONITOR V3") |
| 18 | +(println " Using TokenTransfer events for accurate data") |
18 | 19 | (println "═══════════════════════════════════════════════════════════════") |
19 | 20 | (println "") |
20 | 21 |
|
21 | 22 | ;; Initialize counters |
22 | 23 | (define buy-count 0) |
23 | 24 | (define sell-count 0) |
24 | 25 | (define graduation-count 0) |
| 26 | +(define transfer-count 0) |
25 | 27 |
|
26 | 28 | ;; Main event loop (60 seconds) |
27 | 29 | (define start-time (now)) |
|
33 | 35 | (for (event events) |
34 | 36 | (define event-type (get event "type")) |
35 | 37 |
|
36 | | - (if (= event-type "log_message") |
| 38 | + ;; ═══════════════════════════════════════════════════════════ |
| 39 | + ;; PROCESS TOKEN TRANSFER EVENTS (BEST SOURCE OF DATA) |
| 40 | + ;; ═══════════════════════════════════════════════════════════ |
| 41 | + (if (= event-type "token_transfer") |
37 | 42 | (do |
38 | | - (define logs (get event "logs")) |
| 43 | + (set! transfer-count (+ transfer-count 1)) |
39 | 44 | (define signature (get event "signature")) |
40 | | - (define slot (get event "slot")) |
41 | | - (define logs-text (str logs)) |
42 | | - |
43 | | - ;; Extract wallet from first invoke [1] |
44 | | - (define wallet "Unknown") |
45 | | - (for (line logs) |
46 | | - (if (and (string-contains line "invoke [1]") |
47 | | - (not (string-contains line "ComputeBudget")) |
48 | | - (not (string-contains line "11111111"))) |
49 | | - (do |
50 | | - (define parts (split line " ")) |
51 | | - (if (>= (length parts) 2) |
52 | | - (set! wallet (get parts 1)) |
53 | | - null)) |
54 | | - null)) |
55 | | - |
56 | | - ;; Extract program ID |
57 | | - (define program-id "Unknown") |
58 | | - (define program-name "Unknown") |
59 | | - (for (line logs) |
60 | | - (if (and (string-contains line "invoke [1]") |
61 | | - (not (string-contains line "ComputeBudget")) |
62 | | - (not (string-contains line "11111111"))) |
63 | | - (do |
64 | | - (define parts (split line " ")) |
65 | | - (if (>= (length parts) 2) |
66 | | - (do |
67 | | - (set! program-id (get parts 1)) |
68 | | - (if (string-contains program-id "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") |
69 | | - (set! program-name "Pump.fun Core") null) |
70 | | - (if (string-contains program-id "MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e") |
71 | | - (set! program-name "Pump.fun Wrapper") null) |
72 | | - (if (string-contains program-id "BLUR9cL8HqZzu5bSaC7VRX25RCG93Hv3T6NPyKxQhWUT") |
73 | | - (set! program-name "BLUR Bot") null) |
74 | | - (if (string-contains program-id "GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb") |
75 | | - (set! program-name "GMgn Bot") null)) |
76 | | - null)) |
77 | | - null)) |
78 | | - |
79 | | - ;; Extract SOL amount |
80 | | - (define sol-amount "Unknown") |
81 | | - (for (line logs) |
82 | | - (if (string-contains line "sol_received:") |
83 | | - (do |
84 | | - (define parts (split line ":")) |
85 | | - (if (>= (length parts) 3) |
86 | | - (do |
87 | | - (define amount-str (get parts 2)) |
88 | | - (define amount-parts (split amount-str ",")) |
89 | | - (if (>= (length amount-parts) 1) |
90 | | - (set! sol-amount (get amount-parts 0)) |
91 | | - null)) |
92 | | - null)) |
93 | | - null)) |
94 | | - |
95 | | - ;; Extract token program |
96 | | - (define token-program "Unknown") |
97 | | - (for (line logs) |
98 | | - (if (string-contains line "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") |
99 | | - (set! token-program "Token2022") null) |
100 | | - (if (string-contains line "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") |
101 | | - (set! token-program "SPL Token") null)) |
102 | | - |
103 | | - ;; Extract token mint and amount from logs |
104 | | - ;; PUMP.FUN TOKEN MINTS ALL END WITH "pump"! |
105 | | - (define token-mint "Unknown") |
106 | | - (define token-amount "Unknown") |
107 | | - (define program-data-b64 "") |
108 | | - |
109 | | - ;; Strategy 1: Look for base58 addresses ending in "pump" |
110 | | - ;; These are Pump.fun token mints created via bonding curve |
111 | | - (for (line logs) |
112 | | - ;; Check ALL lines for addresses ending in "pump" |
113 | | - (define parts (split line " ")) |
114 | | - (for (part parts) |
115 | | - ;; Check if this part is a long base58 address ending in "pump" |
116 | | - (if (and (> (length part) 32) |
117 | | - (string-contains part "pump")) |
118 | | - (do |
119 | | - ;; Additional check: must END with "pump" not just contain it |
120 | | - ;; Get last 4 chars |
121 | | - (define part-len (length part)) |
122 | | - (if (>= part-len 4) |
123 | | - (do |
124 | | - (define last-4 (substring part (- part-len 4) part-len)) |
125 | | - (if (= last-4 "pump") |
126 | | - (set! token-mint part) |
127 | | - null)) |
128 | | - null)) |
129 | | - null)) |
130 | | - |
131 | | - ;; Strategy 2: Extract "Program data:" for amount decoding |
132 | | - (if (string-contains line "Program data:") |
133 | | - (do |
134 | | - (define parts (split line ":")) |
135 | | - (if (>= (length parts) 2) |
136 | | - (do |
137 | | - ;; Get the base64 part (after "Program data:") |
138 | | - (define data-part (get parts 1)) |
139 | | - ;; MUST trim whitespace before base64-decode |
140 | | - (set! program-data-b64 (trim data-part))) |
141 | | - null)) |
142 | | - null)) |
143 | | - |
144 | | - ;; Strategy 3: Decode Program data to extract token amount (Borsh format) |
145 | | - ;; Pump.fun Borsh structure: |
146 | | - ;; - Bytes 0-7: Unknown/discriminator |
147 | | - ;; - Bytes 8-15: Token amount (u64 little-endian) |
148 | | - ;; - Bytes 16+: Other bonding curve state |
149 | | - (if (> (length program-data-b64) 0) |
| 45 | + (define from-wallet (get event "from")) |
| 46 | + (define to-wallet (get event "to")) |
| 47 | + (define token-mint (get event "token")) |
| 48 | + (define amount (get event "amount")) |
| 49 | + (define decimals (get event "decimals")) |
| 50 | + |
| 51 | + ;; Check if this is a Pump.fun token (ends with "pump") |
| 52 | + (define is-pumpfun-token false) |
| 53 | + (if (> (length token-mint) 4) |
150 | 54 | (do |
151 | | - ;; base64-decode-raw returns hex string (binary-safe) |
152 | | - (define hex-data (base64-decode-raw program-data-b64)) |
153 | | - |
154 | | - ;; Pump.fun Borsh layout - try common offsets |
155 | | - ;; Offset 8 = token amount (most common) |
156 | | - (if (>= (length hex-data) 32) ;; 32 hex chars = 16 bytes minimum |
157 | | - (do |
158 | | - ;; Parse u64 at byte offset 8 (= hex offset 16) |
159 | | - (define amount-raw (hex-to-u64-le hex-data 8)) |
160 | | - ;; Format with decimals (Pump.fun tokens usually have 6 decimals) |
161 | | - (define amount-float (/ amount-raw 1000000.0)) |
162 | | - (set! token-amount (str amount-float " tokens (raw: " amount-raw ")"))) |
163 | | - (set! token-amount (str "Program data too short: " (length hex-data) " hex chars")))) |
| 55 | + (define mint-len (length token-mint)) |
| 56 | + (define last-4 (substring token-mint (- mint-len 4) mint-len)) |
| 57 | + (if (= last-4 "pump") |
| 58 | + (set! is-pumpfun-token true) |
| 59 | + null)) |
164 | 60 | null) |
165 | 61 |
|
166 | | - ;; Try to find token_transfer event as fallback |
167 | | - (for (evt events) |
168 | | - (if (= (get evt "type") "token_transfer") |
169 | | - (do |
170 | | - (if (= (get evt "signature") signature) |
171 | | - (do |
172 | | - (set! token-mint (get evt "token")) |
173 | | - (set! token-amount (str (get evt "amount") " (decimals: " (get evt "decimals") ")"))) |
174 | | - null)) |
175 | | - null)) |
176 | | - |
177 | | - ;; Display BUY transactions |
178 | | - (if (string-contains logs-text "Instruction: Buy") |
| 62 | + ;; Only display Pump.fun token transfers |
| 63 | + (if is-pumpfun-token |
179 | 64 | (do |
180 | | - (set! buy-count (+ buy-count 1)) |
181 | 65 | (println "") |
182 | 66 | (println "═══════════════════════════════════════════════════════════════") |
183 | | - (println (str "📈 BUY #" buy-count " | Slot: " slot " | Time: " (now))) |
| 67 | + (println (str "💸 TOKEN TRANSFER #" transfer-count " | Time: " (now))) |
184 | 68 | (println "═══════════════════════════════════════════════════════════════") |
185 | 69 | (println (str "Signature: " signature)) |
186 | | - (println (str "Wallet: " wallet)) |
187 | | - (println (str "Program: " program-name " (" program-id ")")) |
188 | | - (println (str "Token Std: " token-program)) |
| 70 | + (println (str "From: " from-wallet)) |
| 71 | + (println (str "To: " to-wallet)) |
189 | 72 | (println (str "Token Mint: " token-mint)) |
190 | | - (println (str "Token Amount: " token-amount)) |
191 | | - (println (str "SOL Spent: " sol-amount " lamports")) |
| 73 | + (println (str "Amount: " amount " (decimals: " decimals ")")) |
192 | 74 | (println (str "osvm.ai: https://osvm.ai/tx/" signature)) |
193 | 75 | (println "═══════════════════════════════════════════════════════════════")) |
| 76 | + null)) |
| 77 | + null) |
| 78 | + |
| 79 | + ;; ═══════════════════════════════════════════════════════════ |
| 80 | + ;; PROCESS LOG MESSAGES (FOR BUY/SELL/GRADUATION DETECTION) |
| 81 | + ;; ═══════════════════════════════════════════════════════════ |
| 82 | + (if (= event-type "log_message") |
| 83 | + (do |
| 84 | + (define logs (get event "logs")) |
| 85 | + (define signature (get event "signature")) |
| 86 | + (define slot (get event "slot")) |
| 87 | + (define logs-text (str logs)) |
| 88 | + |
| 89 | + ;; Detect transaction type from logs |
| 90 | + (if (string-contains logs-text "Instruction: Buy") |
| 91 | + (set! buy-count (+ buy-count 1)) |
194 | 92 | null) |
195 | 93 |
|
196 | | - ;; Display SELL transactions |
197 | 94 | (if (string-contains logs-text "Instruction: Sell") |
198 | | - (do |
199 | | - (set! sell-count (+ sell-count 1)) |
200 | | - (println "") |
201 | | - (println "═══════════════════════════════════════════════════════════════") |
202 | | - (println (str "📉 SELL #" sell-count " | Slot: " slot " | Time: " (now))) |
203 | | - (println "═══════════════════════════════════════════════════════════════") |
204 | | - (println (str "Signature: " signature)) |
205 | | - (println (str "Wallet: " wallet)) |
206 | | - (println (str "Program: " program-name " (" program-id ")")) |
207 | | - (println (str "Token Std: " token-program)) |
208 | | - (println (str "Token Mint: " token-mint)) |
209 | | - (println (str "Token Amount: " token-amount)) |
210 | | - (println (str "SOL Received: " sol-amount " lamports")) |
211 | | - (println (str "osvm.ai: https://osvm.ai/tx/" signature)) |
212 | | - (println "═══════════════════════════════════════════════════════════════")) |
| 95 | + (set! sell-count (+ sell-count 1)) |
213 | 96 | null) |
214 | 97 |
|
215 | | - ;; Display graduations |
216 | 98 | (if (or (string-contains logs-text "raydium") |
217 | 99 | (string-contains logs-text "Instruction: Graduate")) |
218 | 100 | (do |
|
222 | 104 | (println (str "🎓 TOKEN GRADUATION #" graduation-count " - MIGRATING TO RAYDIUM 🚀")) |
223 | 105 | (println "🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓") |
224 | 106 | (println (str "Signature: " signature)) |
225 | | - (println (str "Token: " token-mint)) |
| 107 | + (println (str "Slot: " slot)) |
226 | 108 | (println (str "osvm.ai: https://osvm.ai/tx/" signature)) |
227 | 109 | (println "🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓🎓")) |
228 | 110 | null)) |
| 111 | + null) |
| 112 | + |
| 113 | + ;; ═══════════════════════════════════════════════════════════ |
| 114 | + ;; PROCESS TRANSACTION EVENTS (FOR WALLET/PROGRAM INFO) |
| 115 | + ;; ═══════════════════════════════════════════════════════════ |
| 116 | + (if (= event-type "transaction") |
| 117 | + (do |
| 118 | + ;; Future enhancement: Extract signer, program_ids, fees |
| 119 | + null) |
229 | 120 | null))) |
230 | 121 |
|
231 | 122 | ;; Summary |
|
234 | 125 | (println "═══════════════════════════════════════════════════════════════") |
235 | 126 | (println " FORENSICS SUMMARY") |
236 | 127 | (println "═══════════════════════════════════════════════════════════════") |
237 | | -(println (str "Duration: " duration " seconds")) |
238 | | -(println (str "Buy Txs: " buy-count)) |
239 | | -(println (str "Sell Txs: " sell-count)) |
240 | | -(println (str "Graduations: " graduation-count)) |
241 | | -(println (str "Total Events: " (+ buy-count sell-count graduation-count))) |
| 128 | +(println (str "Duration: " duration " seconds")) |
| 129 | +(println (str "Buy Txs: " buy-count)) |
| 130 | +(println (str "Sell Txs: " sell-count)) |
| 131 | +(println (str "Token Transfers: " transfer-count)) |
| 132 | +(println (str "Graduations: " graduation-count)) |
| 133 | +(println (str "Total Events: " (+ buy-count sell-count graduation-count transfer-count))) |
242 | 134 | (println "═══════════════════════════════════════════════════════════════") |
243 | 135 |
|
244 | 136 | (stream-close stream-id) |
|
0 commit comments