-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add Solana indexer support #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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.
…ount types in Instruction
…mprove error handling
…roved error handling and performance
| 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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}) | ||
| } |
There was a problem hiding this comment.
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
| 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 | ||
| } |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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
| Confirmations: 1, | ||
| Status: types.CalculateStatus(1), |
There was a problem hiding this comment.
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
| 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), | ||
| }) |
There was a problem hiding this comment.
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?
| 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), | ||
| }) | ||
| } |
There was a problem hiding this comment.
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
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.errfield 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
blockhashandpreviousBlockhash. Furthermore, all blocks are fetched withfinalizedcommitment, which provides the strongest guarantee against reorgs and ensures data consistency.