Skip to content

Conversation

@Woft257
Copy link

@Woft257 Woft257 commented Jan 10, 2026

This PR adds full indexing support for the Solana blockchain, capable of processing both native SOL and SPL token transfers.

The implementation is built to be resilient and aware of Solana's specific architecture. It correctly handles "skipped slots" as a normal chain event rather than an error, which prevents false positives and ensures the indexer can sync reliably.

To guarantee data integrity, transaction and block processing is robust:

  • Failed Transactions: The indexer inspects the meta.err field for each transaction. If a transaction has failed on-chain, it is skipped, ensuring that only successful transfers are processed and indexed.

  • Reorg Handling & Data Consistency: The indexer's existing reorg detection logic is fully applied to Solana by comparing the blockhash and previousBlockhash. Furthermore, all blocks are fetched with finalized commitment, which provides the strongest guarantee against reorgs and ensures data consistency.

- Implemented Solana indexer in `internal/indexer/solana.go` with methods to retrieve block information and transactions.
- Created Solana RPC client in `internal/rpc/solana/client.go` to interact with Solana's JSON-RPC API.
- Defined Solana API interface in `internal/rpc/solana/api.go` for abstraction.
- Added necessary types for Solana RPC responses in `internal/rpc/solana/types.go`.
- Updated worker factory in `internal/worker/factory.go` to include Solana indexer initialization.
Comment on lines +53 to +59
if len(results) == 0 || results[0].Error != nil {
if len(results) > 0 && results[0].Error != nil {
return nil, fmt.Errorf(results[0].Error.Message)
}
return nil, fmt.Errorf("block not found")
}
return results[0].Block, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code is unreadable, bad practice

please refactor

should handle seperate for each branch
Also got an warning non-constant format string in call to fmt.Errorf (printf default)

)

const solAssetAddress = "SOL"
const solanaMaxParallelFactor = 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the max prallel factor is 8? How can we come up with this number

addr string
delta uint64
}{addr: k.Pubkey, delta: postAmt - preAmt})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a reliable way to check is native transfer, please find an other solution

Comment on lines +267 to +406
si, ri := 0, 0
for si < len(solSenders) && ri < len(solReceivers) {
s := &solSenders[si]
r := &solReceivers[ri]

amt := s.delta
if r.delta < amt {
amt = r.delta
}
if amt == 0 {
if s.delta == 0 {
si++
}
if r.delta == 0 {
ri++
}
continue
}

out = append(out, types.Transaction{
TxHash: txHash,
NetworkId: networkID,
BlockNumber: slot,
FromAddress: s.addr,
ToAddress: r.addr,
AssetAddress: solAssetAddress,
Amount: fmt.Sprintf("%d", amt),
Type: "sol_transfer",
TxFee: fee,
Timestamp: ts,
Confirmations: 1,
Status: types.CalculateStatus(1),
})

s.delta -= amt
r.delta -= amt
if s.delta == 0 {
si++
}
if r.delta == 0 {
ri++
}
}
}

// SPL token deltas via token balances (pre/post)
preTok := map[string]map[string]uint64{} // owner -> mint -> amount
postTok := map[string]map[string]uint64{} // owner -> mint -> amount

for _, tb := range tx.Meta.PreTokenBalances {
if tb.Owner == "" || tb.Mint == "" {
continue
}
amt, err := strconv.ParseUint(tb.UiTokenAmount.Amount, 10, 64)
if err != nil {
continue
}
if preTok[tb.Owner] == nil {
preTok[tb.Owner] = map[string]uint64{}
}
preTok[tb.Owner][tb.Mint] = amt
}
for _, tb := range tx.Meta.PostTokenBalances {
if tb.Owner == "" || tb.Mint == "" {
continue
}
amt, err := strconv.ParseUint(tb.UiTokenAmount.Amount, 10, 64)
if err != nil {
continue
}
if postTok[tb.Owner] == nil {
postTok[tb.Owner] = map[string]uint64{}
}
postTok[tb.Owner][tb.Mint] = amt
}

// Build token deltas per owner/mint
type deltaItem struct {
addr string
mint string
delta uint64
}
var tokenSenders []deltaItem
var tokenReceivers []deltaItem

seenOwners := map[string]bool{}
for o := range preTok {
seenOwners[o] = true
}
for o := range postTok {
seenOwners[o] = true
}

for owner := range seenOwners {
preM := preTok[owner]
postM := postTok[owner]

seenMints := map[string]bool{}
for m := range preM {
seenMints[m] = true
}
for m := range postM {
seenMints[m] = true
}

for mint := range seenMints {
preAmt := uint64(0)
postAmt := uint64(0)
if preM != nil {
preAmt = preM[mint]
}
if postM != nil {
postAmt = postM[mint]
}
if preAmt == postAmt {
continue
}
if preAmt > postAmt {
tokenSenders = append(tokenSenders, deltaItem{addr: owner, mint: mint, delta: preAmt - postAmt})
} else {
tokenReceivers = append(tokenReceivers, deltaItem{addr: owner, mint: mint, delta: postAmt - preAmt})
}
}
}

// Pair token senders -> receivers per mint (best-effort)
for _, sender := range tokenSenders {
remaining := sender.delta
for i := 0; i < len(tokenReceivers) && remaining > 0; i++ {
r := &tokenReceivers[i]
if r.mint != sender.mint || r.delta == 0 {
continue
}
amt := remaining
if r.delta < amt {
amt = r.delta
}
if amt == 0 {
continue
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not reliable to detect spl token transfer like this please find ano ther soluiton

ToAddress: r.addr,
AssetAddress: sender.mint,
Amount: fmt.Sprintf("%d", amt),
Type: "token_transfer",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should take a look at other chains and reuse similar value

Comment on lines +419 to +420
Confirmations: 1,
Status: types.CalculateStatus(1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmaiton and status are wrong. should remove as only Bitcoin need those two status, we will not need it for solana. Can check example in Tron and Evm

Comment on lines +428 to +442
if remaining > 0 {
out = append(out, types.Transaction{
TxHash: txHash,
NetworkId: networkID,
BlockNumber: slot,
FromAddress: sender.addr,
ToAddress: "",
AssetAddress: sender.mint,
Amount: fmt.Sprintf("%d", remaining),
Type: "token_transfer",
TxFee: fee,
Timestamp: ts,
Confirmations: 1,
Status: types.CalculateStatus(1),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of remaining?

Comment on lines +447 to +465
for _, r := range tokenReceivers {
if r.delta == 0 {
continue
}
out = append(out, types.Transaction{
TxHash: txHash,
NetworkId: networkID,
BlockNumber: slot,
FromAddress: "",
ToAddress: r.addr,
AssetAddress: r.mint,
Amount: fmt.Sprintf("%d", r.delta),
Type: "token_receive",
TxFee: fee,
Timestamp: ts,
Confirmations: 1,
Status: types.CalculateStatus(1),
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the purpose of this code block

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants