Skip to content

Commit ba6c3db

Browse files
authored
Merge pull request #83 from fystack/enhance/solana
Add block hash and transfer index for solana
2 parents 552b436 + 0805a6b commit ba6c3db

2 files changed

Lines changed: 144 additions & 25 deletions

File tree

internal/indexer/solana.go

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ func solanaParseTokenTransfer(ix solana.Instruction, accountKeys []solana.Accoun
466466

467467
func (s *SolanaIndexer) extractSolanaTransfers(networkID string, slot uint64, ts uint64, b *solana.GetBlockResult) []types.Transaction {
468468
out := make([]types.Transaction, 0)
469-
for _, tx := range b.Transactions {
469+
for txIdx, tx := range b.Transactions {
470470
if tx.Meta == nil {
471471
continue
472472
}
@@ -509,22 +509,27 @@ func (s *SolanaIndexer) extractSolanaTransfers(networkID string, slot uint64, ts
509509
}
510510
}
511511

512+
transferIdx := 0
513+
512514
appendNative := func(from, to string, lamports uint64) {
513515
if !s.isMonitoredTransfer(from, to) {
514516
return
515517
}
516518
out = append(out, types.Transaction{
517-
TxHash: txHash,
518-
NetworkId: networkID,
519-
BlockNumber: slot,
520-
FromAddress: from,
521-
ToAddress: to,
522-
AssetAddress: "",
523-
Amount: strconv.FormatUint(lamports, 10),
524-
Type: constant.TxTypeNativeTransfer,
525-
TxFee: fee,
526-
Timestamp: ts,
519+
TxHash: txHash,
520+
NetworkId: networkID,
521+
BlockNumber: slot,
522+
BlockHash: b.Blockhash,
523+
TransferIndex: fmt.Sprintf("%d:%d", txIdx, transferIdx),
524+
FromAddress: from,
525+
ToAddress: to,
526+
AssetAddress: "",
527+
Amount: strconv.FormatUint(lamports, 10),
528+
Type: constant.TxTypeNativeTransfer,
529+
TxFee: fee,
530+
Timestamp: ts,
527531
})
532+
transferIdx++
528533
}
529534

530535
appendSPL := func(srcTokenAcc, dstTokenAcc, mint string, amount uint64) {
@@ -546,17 +551,20 @@ func (s *SolanaIndexer) extractSolanaTransfers(networkID string, slot uint64, ts
546551
}
547552

548553
out = append(out, types.Transaction{
549-
TxHash: txHash,
550-
NetworkId: networkID,
551-
BlockNumber: slot,
552-
FromAddress: fromOwner,
553-
ToAddress: toOwner,
554-
AssetAddress: mint,
555-
Amount: strconv.FormatUint(amount, 10),
556-
Type: constant.TxTypeTokenTransfer,
557-
TxFee: fee,
558-
Timestamp: ts,
554+
TxHash: txHash,
555+
NetworkId: networkID,
556+
BlockNumber: slot,
557+
BlockHash: b.Blockhash,
558+
TransferIndex: fmt.Sprintf("%d:%d", txIdx, transferIdx),
559+
FromAddress: fromOwner,
560+
ToAddress: toOwner,
561+
AssetAddress: mint,
562+
Amount: strconv.FormatUint(amount, 10),
563+
Type: constant.TxTypeTokenTransfer,
564+
TxFee: fee,
565+
Timestamp: ts,
559566
})
567+
transferIdx++
560568
}
561569

562570
processIx := func(ix solana.Instruction) {

internal/indexer/solana_test.go

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func newTestSolanaIndexer() *SolanaIndexer {
3131
// so it can be fed into extractSolanaTransfers.
3232
func 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

Comments
 (0)