@@ -31,6 +31,8 @@ func newTestSolanaIndexer() *SolanaIndexer {
3131// so it can be fed into extractSolanaTransfers.
3232func txToBlockResult (tx * solana.GetTransactionResult ) * solana.GetBlockResult {
3333 return & solana.GetBlockResult {
34+ Blockhash : "testhash123" ,
35+ PreviousBlockhash : "parenthash456" ,
3436 Transactions : []solana.BlockTxn {
3537 {
3638 Meta : tx .Meta ,
@@ -40,6 +42,111 @@ func txToBlockResult(tx *solana.GetTransactionResult) *solana.GetBlockResult {
4042 }
4143}
4244
45+ func TestSolanaBlockHashAndTransferIndex (t * testing.T ) {
46+ idx := newTestSolanaIndexer ()
47+
48+ blockHash := "9xJ7rGWdmA9Y4qKkZn1bFwP3KpvLcAhRsL1oXrNBp4v"
49+ makeTxnEnvelope := func (sig string , keys []solana.AccountKey , ixs []solana.Instruction ) solana.TxnEnvelope {
50+ env := solana.TxnEnvelope {Signatures : []string {sig }}
51+ env .Message .AccountKeys = keys
52+ env .Message .Instructions = ixs
53+ return env
54+ }
55+
56+ block := & solana.GetBlockResult {
57+ Blockhash : blockHash ,
58+ PreviousBlockhash : "parentHash123" ,
59+ Transactions : []solana.BlockTxn {
60+ {
61+ Meta : & solana.TxnMeta {Fee : 5000 },
62+ Transaction : makeTxnEnvelope ("sig1" ,
63+ []solana.AccountKey {
64+ {Pubkey : "sender1" },
65+ {Pubkey : "receiver1" },
66+ {Pubkey : "sender2" },
67+ {Pubkey : "receiver2" },
68+ {Pubkey : solanaSystemProgramID },
69+ },
70+ []solana.Instruction {
71+ {
72+ Program : "system" ,
73+ ProgramId : solanaSystemProgramID ,
74+ Parsed : map [string ]any {
75+ "type" : "transfer" ,
76+ "info" : map [string ]any {
77+ "source" : "sender1" ,
78+ "destination" : "receiver1" ,
79+ "lamports" : float64 (1000000 ),
80+ },
81+ },
82+ },
83+ {
84+ Program : "system" ,
85+ ProgramId : solanaSystemProgramID ,
86+ Parsed : map [string ]any {
87+ "type" : "transfer" ,
88+ "info" : map [string ]any {
89+ "source" : "sender2" ,
90+ "destination" : "receiver2" ,
91+ "lamports" : float64 (2000000 ),
92+ },
93+ },
94+ },
95+ },
96+ ),
97+ },
98+ {
99+ Meta : & solana.TxnMeta {Fee : 5000 },
100+ Transaction : makeTxnEnvelope ("sig2" ,
101+ []solana.AccountKey {
102+ {Pubkey : "sender3" },
103+ {Pubkey : "receiver3" },
104+ {Pubkey : solanaSystemProgramID },
105+ },
106+ []solana.Instruction {
107+ {
108+ Program : "system" ,
109+ ProgramId : solanaSystemProgramID ,
110+ Parsed : map [string ]any {
111+ "type" : "transfer" ,
112+ "info" : map [string ]any {
113+ "source" : "sender3" ,
114+ "destination" : "receiver3" ,
115+ "lamports" : float64 (3000000 ),
116+ },
117+ },
118+ },
119+ },
120+ ),
121+ },
122+ },
123+ }
124+
125+ transfers := idx .extractSolanaTransfers ("solana-mainnet" , 100 , 1234567890 , block )
126+
127+ require .Len (t , transfers , 3 )
128+
129+ // All transfers should have BlockHash set
130+ for _ , tx := range transfers {
131+ assert .Equal (t , blockHash , tx .BlockHash , "BlockHash should be propagated" )
132+ assert .NotEmpty (t , tx .TransferIndex , "TransferIndex should be set" )
133+ }
134+
135+ // TransferIndexes should be unique
136+ seen := map [string ]bool {}
137+ for _ , tx := range transfers {
138+ key := tx .TxHash + ":" + tx .TransferIndex
139+ assert .False (t , seen [key ], "TransferIndex should be unique within block, duplicate: %s" , key )
140+ seen [key ] = true
141+ }
142+
143+ // First tx has two transfers: 0:0 and 0:1
144+ assert .Equal (t , "0:0" , transfers [0 ].TransferIndex )
145+ assert .Equal (t , "0:1" , transfers [1 ].TransferIndex )
146+ // Second tx has one transfer: 1:0
147+ assert .Equal (t , "1:0" , transfers [2 ].TransferIndex )
148+ }
149+
43150// TestParseSPLTransfer tests parsing of SPL Transfer (opcode 3) instruction from a real mainnet tx.
44151// Transaction: 4dc8JLGc2ee2FHXhEfDEXNuG62TZjwvSUGiCwfPnXpiMfCEAcTjg6LXnqEAV9fzbHXaWAiNcNEDrSQMWYmfy9cTv
45152// This is a USDC transfer using the Transfer instruction (not TransferChecked).
@@ -81,10 +188,12 @@ func TestParseSPLTransfer(t *testing.T) {
81188 assert .Equal (t , "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" , tokenTransfer .AssetAddress , "AssetAddress should be USDC mint" )
82189 assert .NotEmpty (t , tokenTransfer .FromAddress , "FromAddress (owner) should be resolved" )
83190 assert .NotEmpty (t , tokenTransfer .ToAddress , "ToAddress (owner) should be resolved" )
191+ assert .Equal (t , "testhash123" , tokenTransfer .BlockHash , "BlockHash should be propagated from block" )
192+ assert .NotEmpty (t , tokenTransfer .TransferIndex , "TransferIndex should be set" )
84193
85- t .Logf ("Transfer: from=%s to=%s amount=%s token=%s" ,
194+ t .Logf ("Transfer: from=%s to=%s amount=%s token=%s transferIndex=%s " ,
86195 tokenTransfer .FromAddress , tokenTransfer .ToAddress ,
87- tokenTransfer .Amount , tokenTransfer .AssetAddress )
196+ tokenTransfer .Amount , tokenTransfer .AssetAddress , tokenTransfer . TransferIndex )
88197}
89198
90199// TestParseSPLTransferChecked tests parsing of SPL TransferChecked (opcode 12) instruction from a real mainnet tx.
@@ -128,8 +237,10 @@ func TestParseSPLTransferChecked(t *testing.T) {
128237 assert .Equal (t , "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" , tokenTransfer .AssetAddress , "AssetAddress should be USDC mint" )
129238 assert .NotEmpty (t , tokenTransfer .FromAddress , "FromAddress (owner) should be resolved" )
130239 assert .NotEmpty (t , tokenTransfer .ToAddress , "ToAddress (owner) should be resolved" )
240+ assert .Equal (t , "testhash123" , tokenTransfer .BlockHash , "BlockHash should be propagated from block" )
241+ assert .NotEmpty (t , tokenTransfer .TransferIndex , "TransferIndex should be set" )
131242
132- t .Logf ("TransferChecked: from=%s to=%s amount=%s token=%s" ,
243+ t .Logf ("TransferChecked: from=%s to=%s amount=%s token=%s transferIndex=%s " ,
133244 tokenTransfer .FromAddress , tokenTransfer .ToAddress ,
134- tokenTransfer .Amount , tokenTransfer .AssetAddress )
245+ tokenTransfer .Amount , tokenTransfer .AssetAddress , tokenTransfer . TransferIndex )
135246}
0 commit comments