@@ -244,3 +244,129 @@ func TestParseSPLTransferChecked(t *testing.T) {
244244 tokenTransfer .FromAddress , tokenTransfer .ToAddress ,
245245 tokenTransfer .Amount , tokenTransfer .AssetAddress , tokenTransfer .TransferIndex )
246246}
247+
248+ // TestParseBatchSOLTransfer tests parsing of a batch transfer transaction containing
249+ // 20 native SOL transfers (1 lamport each) from a single sender to 20 different recipients.
250+ // Transaction: 5dKRr7cF6pNiuhKYY8RDF5rxTjbFWFGAzKgBXoYrPcg2uMx14uwfopiX1XiyZuu3aihMMRqzSSX9jQcWnamqw5DK
251+ func TestParseBatchSOLTransfer (t * testing.T ) {
252+ if testing .Short () {
253+ t .Skip ("skipping integration test" )
254+ }
255+
256+ ctx := context .Background ()
257+ c := newTestSolanaClient ()
258+
259+ txSig := "5dKRr7cF6pNiuhKYY8RDF5rxTjbFWFGAzKgBXoYrPcg2uMx14uwfopiX1XiyZuu3aihMMRqzSSX9jQcWnamqw5DK"
260+ txResult , err := c .GetTransaction (ctx , txSig )
261+ require .NoError (t , err )
262+ require .NotNil (t , txResult , "transaction should exist on mainnet" )
263+
264+ idx := newTestSolanaIndexer ()
265+ block := txToBlockResult (txResult )
266+
267+ ts := uint64 (0 )
268+ if txResult .BlockTime != nil {
269+ ts = uint64 (* txResult .BlockTime )
270+ }
271+ transfers := idx .extractSolanaTransfers ("solana-mainnet" , txResult .Slot , ts , block )
272+
273+ // This transaction contains 20 native SOL transfers of 1 lamport each
274+ expectedRecipients := []string {
275+ "5P1E2RDvWWsQYtHgC9Ng3SejPq7tLdk697tNWfkxwx5g" ,
276+ "JCxSuQwRa2Qbtxjrv9Csf2B8AtCMiLjfr24Bb27yheFL" ,
277+ "4Y84ZayuVutuW2e1Lq8qfq5JYwYkTn8CtutmZs3jW8oM" ,
278+ "9rRE1bEPcQxf3LSJphSd1jHbDVi4dLYWgY9g61YTCHdW" ,
279+ "3dUBc9cLiF92uWCdZVGmB7iKPTHVioeE5UqA8PEjUYXs" ,
280+ "FeAuJTC8wZypEuTQteys9PRdBPAL3ik8VuViDtQNm2tb" ,
281+ "BMvWVyMFoDhfwvqDG4q2uGmnP4sr4FhqT1hDSPbrbGRX" ,
282+ "3KSTPVNHmWvkwy3JcuKQH8sLogv1Gf9rauj8Z1TpPdbG" ,
283+ "DHY4ANQod7Ff67ojZPgzWePwHNSBeJwbWmye2SwR6F3V" ,
284+ "B9eWxGfXGsLhMPx65mdFiSayhqVAxWG4TGzaFpuTWQmH" ,
285+ "Avr7d3kwUcV8cUhzDmpbVjzPDCni9FSJYyNaDjuFPLZt" ,
286+ "HPDN6Ro6gPgYbNgnv7yhFCpyY3uYzT18epFxRFNZ985w" ,
287+ "EYidortrJzJGkR3r2gFg2sJVM6WJQNE7yZdzvhndGPVQ" ,
288+ "E4oy1PetbUo7UKww5Dmy9gHyNgrtQWPsC5DHBsVu3KQo" ,
289+ "H6EJh7pyygR8ghgPxe1j4NE3FC7BuzC4S2Cybw5fJMcg" ,
290+ "5E81HKzq7vXPgGcH3ZndGW56DtLBPC9rJD5RmAMdbE3Y" ,
291+ "H1PQRtvSEH2HwwtZUnBfxEerwbhPHhUvTCtDF66NSCY4" ,
292+ "9mSFaSJgQotHmL14xSMcRgNC9hnidNfGkyJ2fhvizKuj" ,
293+ "GLvb7P4q7AkX1j7r4S2fr4uoLWqDLWRsPkJqMMcNR9CE" ,
294+ "5XFz3x79Hi8zzQb8RuzNqzoqXkyxRCgDyvjK7BrfCKML" ,
295+ }
296+
297+ const expectedSender = "AZWibhw2cmpVFt4b45XGhiB8vSmYc2LfMGyHE3Lb97cM"
298+
299+ // All transfers should be native
300+ var nativeTransfers []types.Transaction
301+ for _ , tx := range transfers {
302+ if tx .Type == constant .TxTypeNativeTransfer {
303+ nativeTransfers = append (nativeTransfers , tx )
304+ }
305+ }
306+
307+ require .Len (t , nativeTransfers , 20 , "should detect all 20 native SOL transfers in the batch" )
308+
309+ // Verify each transfer
310+ for i , tx := range nativeTransfers {
311+ assert .Equal (t , txSig , tx .TxHash , "transfer %d: TxHash should match" , i )
312+ assert .Equal (t , expectedSender , tx .FromAddress , "transfer %d: sender should match" , i )
313+ assert .Equal (t , expectedRecipients [i ], tx .ToAddress , "transfer %d: recipient should match" , i )
314+ assert .Equal (t , "1" , tx .Amount , "transfer %d: amount should be 1 lamport" , i )
315+ assert .Empty (t , tx .AssetAddress , "transfer %d: AssetAddress should be empty for native SOL" , i )
316+ }
317+
318+ // Verify no token transfers were detected
319+ for _ , tx := range transfers {
320+ assert .NotEqual (t , constant .TxTypeTokenTransfer , tx .Type , "should not detect any token transfers" )
321+ }
322+
323+ t .Logf ("Batch transfer: %d native SOL transfers from %s (1 lamport each)" , len (nativeTransfers ), expectedSender )
324+ }
325+
326+ // TestParseSquadsMultisigTransfer tests parsing of a Squads Multisig (SMPLecH534NA9acpos4G6x7uf3LWbCAwZQE9e8ZekMu)
327+ // transaction that CPI-calls Token-2022 with transferCheckedWithFee.
328+ // Transaction: 2Sqm2FwnSdTLQ5tfbpCKafWiQDwynmDZrkvGxCbpin3mwPHdmRKNDq6drv5rnosp9is86JwScQLnyWHbV9TfYTDj
329+ func TestParseSquadsMultisigTransfer (t * testing.T ) {
330+ if testing .Short () {
331+ t .Skip ("skipping integration test" )
332+ }
333+
334+ ctx := context .Background ()
335+ c := newTestSolanaClient ()
336+
337+ txSig := "2Sqm2FwnSdTLQ5tfbpCKafWiQDwynmDZrkvGxCbpin3mwPHdmRKNDq6drv5rnosp9is86JwScQLnyWHbV9TfYTDj"
338+ txResult , err := c .GetTransaction (ctx , txSig )
339+ require .NoError (t , err )
340+ require .NotNil (t , txResult , "transaction should exist on mainnet" )
341+
342+ idx := newTestSolanaIndexer ()
343+ block := txToBlockResult (txResult )
344+
345+ ts := uint64 (0 )
346+ if txResult .BlockTime != nil {
347+ ts = uint64 (* txResult .BlockTime )
348+ }
349+ transfers := idx .extractSolanaTransfers ("solana-mainnet" , txResult .Slot , ts , block )
350+
351+ // This transaction contains a Token-2022 transferCheckedWithFee via Squads multisig CPI.
352+ // Token: HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC, Amount: 13000000 (0.013 with 9 decimals)
353+ var tokenTransfer * types.Transaction
354+ for i := range transfers {
355+ if transfers [i ].Type == constant .TxTypeTokenTransfer {
356+ tokenTransfer = & transfers [i ]
357+ break
358+ }
359+ }
360+
361+ require .NotNil (t , tokenTransfer , "should detect the Token-2022 transferCheckedWithFee from inner instructions" )
362+
363+ assert .Equal (t , txSig , tokenTransfer .TxHash , "TxHash should match" )
364+ assert .Equal (t , "13000000" , tokenTransfer .Amount , "Amount should be 13000000" )
365+ assert .Equal (t , "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC" , tokenTransfer .AssetAddress , "AssetAddress should be the token mint" )
366+ assert .Equal (t , "BfV1aVZTcN2R8fA9Asa49qsfYzX3rH3jVGkwWAA2muDs" , tokenTransfer .FromAddress , "FromAddress should be the source owner" )
367+ assert .Equal (t , "CvySeQ1FR4CgKo4N1L6UhgCaJ8RWJbcXi32ZppCAnRF4" , tokenTransfer .ToAddress , "ToAddress should be the destination owner" )
368+
369+ t .Logf ("Squads multisig transfer: from=%s to=%s amount=%s token=%s" ,
370+ tokenTransfer .FromAddress , tokenTransfer .ToAddress ,
371+ tokenTransfer .Amount , tokenTransfer .AssetAddress )
372+ }
0 commit comments