Files
libsam/pkg/shreder/txparser.go

315 lines
9.6 KiB
Go
Raw Permalink Normal View History

2025-12-30 11:03:11 +08:00
package shreder
2025-12-26 10:57:37 +08:00
import (
"bytes"
2026-01-28 14:11:34 +08:00
"context"
2025-12-26 10:57:37 +08:00
"fmt"
2026-01-28 14:11:34 +08:00
"io"
2025-12-26 10:57:37 +08:00
"math/big"
2026-01-28 14:11:34 +08:00
"slices"
2026-01-07 11:18:02 +08:00
"strings"
2025-12-26 10:57:37 +08:00
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
const (
wsolMint = "So11111111111111111111111111111111111111112"
tokenProgram = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
)
2026-01-28 14:11:34 +08:00
type Handler struct {
Func func(tx VersionedTransaction, idx int) (TxSignalBatch, error)
Label string
2025-12-26 10:57:37 +08:00
}
2026-01-28 14:11:34 +08:00
type FillAccount interface {
FillAccount(account solana.PublicKey)
2025-12-26 10:57:37 +08:00
}
2026-01-28 14:11:34 +08:00
func init() {
2026-01-30 11:45:57 +08:00
for account := range registered {
defaultFilterAccount = append(defaultFilterAccount, account)
2026-01-28 14:11:34 +08:00
}
//"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
//"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
//"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
2026-01-30 11:45:57 +08:00
defaultFilterAccount = append(defaultFilterAccount,
2026-01-28 14:11:34 +08:00
solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"),
solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"),
solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
)
2026-01-30 11:45:57 +08:00
slices.SortFunc(defaultFilterAccount, func(a, b solana.PublicKey) int {
2026-01-28 14:11:34 +08:00
return bytes.Compare(a[:], b[:])
})
2025-12-26 10:57:37 +08:00
}
2026-01-30 11:45:57 +08:00
type FilterParams struct {
Require []solana.PublicKey
Include []solana.PublicKey
Exclude []solana.PublicKey
}
2026-01-07 21:15:54 +08:00
var (
2026-01-30 11:45:57 +08:00
defaultFilterAccount []solana.PublicKey
2026-01-28 14:11:34 +08:00
2026-01-30 11:45:57 +08:00
registered = map[solana.PublicKey]Handler{
2026-01-28 14:11:34 +08:00
pumpProgramID: {parsePumpInstruction, "pump"},
azczProgramID: {parseAzczInstruction, "azcz"},
f5tfProgramID: {parseF5tfInstruction, "f5tf"},
flasProgramID: {parseFlasInstruction, "flas"},
photonProgramID: {parsePhotonInstruction, "photon"},
pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"},
2026-01-29 16:40:32 +08:00
binanceWalletProgramID: {parseBinanceWalletInstruction, "binancewallet"},
2026-01-28 14:11:34 +08:00
boboProgramID: {parseBoboInstruction, "bobo"},
qtkvProgramID: {parseQtkvInstruction, "qtkv"},
fjszProgramID: {parseFjszInstruction, "fjsz"},
terminalProgramID: {parseTermInstruction, "terminal"},
jupiterV6ProgramID: {parseJupiterV6Instruction, "jupiterv6"},
okxDexRouteV2ProgramID: {parseOkxDexRouteV2Instruction, "okxdexroutev2"},
dflowProgramID: {parseDFlowInstruction, "dflow"},
gmgnProgramID: {parseGMGNInstruction, "gmgn"},
bonkProgramID: {parseBonkInstruction, "bonk"},
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
dlmmProgramID: {parseDlmmInstruction, "dlmm"},
2026-02-02 11:02:10 +08:00
dbotProgramID: {parseDbotInstruction, "dbot"},
2026-02-03 14:06:43 +08:00
tradewizProgramID: {parseTradewizInstruction, "tradewiz"},
2026-01-28 14:11:34 +08:00
}
2026-01-07 21:15:54 +08:00
)
2026-01-28 14:11:34 +08:00
func ParseTransactionForSubscribe(ctx context.Context, update *SubscribeUpdateTransaction, loader *AddressTables, parsed chan<- TxSignal, done chan<- struct{}) {
versioned, err := toVersionedTransaction(update)
if err != nil {
logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
2026-01-28 18:42:34 +08:00
if done != nil {
close(done)
}
2026-01-28 14:11:34 +08:00
return
2026-01-07 21:15:54 +08:00
}
2026-01-28 14:11:34 +08:00
ParseTransaction(ctx, versioned, loader, parsed)
2026-01-28 18:42:34 +08:00
if done != nil {
close(done)
}
2026-01-07 21:15:54 +08:00
}
2026-01-28 14:11:34 +08:00
var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
func FilterTransactionForEntries(versioned VersionedTransaction) bool {
if len(versioned.Instructions) >= 1 {
programKey, _ := versioned.GetAccount(int(versioned.Instructions[0].ProgramIDIndex))
if programKey.Equals(VoteProgram) && len(versioned.AddressTableLookups) == 0 {
return true
}
2026-01-07 21:15:54 +08:00
}
2026-01-28 14:11:34 +08:00
// accounts filter?
include := false
for _, key := range versioned.StaticAccountKeys {
2026-01-30 11:45:57 +08:00
_, include = slices.BinarySearchFunc(defaultFilterAccount, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
2026-01-28 14:11:34 +08:00
return bytes.Compare(key[:], key2[:])
})
if include {
break
2026-01-07 21:15:54 +08:00
}
}
2026-01-28 14:11:34 +08:00
return !include
2026-01-07 21:15:54 +08:00
}
2026-01-30 11:45:57 +08:00
func GetRegisteredHandlers() map[solana.PublicKey]Handler {
return registered
}
func FilterTransactionForEntriesWithFilter(versioned VersionedTransaction, filter map[string]FilterParams) bool {
if len(versioned.Instructions) >= 1 {
programKey, _ := versioned.GetAccount(int(versioned.Instructions[0].ProgramIDIndex))
if programKey.Equals(VoteProgram) && len(versioned.AddressTableLookups) == 0 {
return true
}
}
for _, params := range filter {
excludePass := true
// exclude first
for _, key := range params.Exclude {
if slices.Contains(versioned.StaticAccountKeys, key) {
excludePass = false
break
}
}
requirePass := true
if excludePass {
for _, key := range params.Require {
if !slices.Contains(versioned.StaticAccountKeys, key) {
requirePass = false
break
}
}
}
include := len(params.Include) == 0
if excludePass && requirePass {
for _, key := range params.Include {
if slices.Contains(versioned.StaticAccountKeys, key) {
include = true
break
}
}
}
if excludePass && requirePass && include {
return false
}
}
return true
}
2026-01-28 14:11:34 +08:00
func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader io.Reader, loader *AddressTables, parsed chan<- TxSignal) {
err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) {
// filter out vote transactions
if FilterTransactionForEntries(versioned) {
return
}
go ParseTransaction(ctx, versioned, loader, parsed)
})
if err != nil {
logger.Debug("txparser: failed to parse entries", "error", err)
2026-01-07 21:15:54 +08:00
return
}
}
2026-01-30 11:45:57 +08:00
func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) {
2026-01-28 14:11:34 +08:00
if loader != nil && len(versioned.AddressTableLookups) > 0 {
2026-01-06 16:42:07 +08:00
lookupTableOk := true
2026-01-28 14:11:34 +08:00
for _, lookups := range versioned.AddressTableLookups {
lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.WritableIndexes)
2026-01-08 11:57:57 +08:00
if !lookupTableOk {
2026-01-05 12:45:32 +08:00
break
}
2026-01-06 16:42:07 +08:00
}
if lookupTableOk {
2026-01-28 14:11:34 +08:00
for _, lookups := range versioned.AddressTableLookups {
lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.ReadonlyIndexes)
2026-01-08 11:57:57 +08:00
if !lookupTableOk {
2026-01-06 16:42:07 +08:00
break
}
2026-01-05 12:45:32 +08:00
}
}
}
2026-01-28 14:11:34 +08:00
for i, instruction := range versioned.Instructions {
//load from address table
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
if err != nil {
2025-12-26 10:57:37 +08:00
continue
}
2026-01-30 11:45:57 +08:00
handler, ok := handlers[program]
2026-01-28 14:11:34 +08:00
if !ok {
continue
2026-01-27 14:48:18 +08:00
}
2026-01-28 14:11:34 +08:00
txRes, err := handler.Func(versioned, i)
if err != nil {
if !strings.HasPrefix(err.Error(), "account index") {
logger.Debug("txparser: failed to parse", "label", handler.Label, "err", err, "tx_hash", versioned.Signatures[0].String())
}
2026-01-27 14:48:18 +08:00
continue
}
2026-01-28 14:11:34 +08:00
if txRes != nil {
for _, one := range txRes {
if one == nil {
continue
}
one.Label = handler.Label
one.Block = versioned.Block
select {
case <-ctx.Done():
return
case parsed <- *one:
}
}
2026-01-27 14:48:18 +08:00
}
2026-01-28 14:11:34 +08:00
2026-01-27 14:48:18 +08:00
}
2026-01-28 14:11:34 +08:00
return
2026-01-27 14:48:18 +08:00
}
2026-01-30 11:45:57 +08:00
func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) {
// staticKeys := versioned.Message.StaticAccountKeys
ParseTransactionWithHandler(ctx, versioned, loader, parsed, registered)
}
2026-01-28 14:11:34 +08:00
func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransaction, error) {
2025-12-26 10:57:37 +08:00
if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
2026-01-28 14:11:34 +08:00
return VersionedTransaction{}, fmt.Errorf("transaction is nil")
2025-12-26 10:57:37 +08:00
}
protoTx := update.Transaction
msg := protoTx.Message
2026-01-28 14:11:34 +08:00
versioned := VersionedTransaction{
Signatures: make([]solana.Signature, 0, 10),
StaticAccountKeys: make([]solana.PublicKey, 0, 256),
Instructions: make([]Instructions, 0, 16),
AddressTableLookups: make([]AddressTableLookup, 0, 10),
}
2026-01-07 21:15:54 +08:00
for _, rawSig := range protoTx.Signatures {
versioned.Signatures = append(versioned.Signatures, solana.SignatureFromBytes(rawSig))
2025-12-26 10:57:37 +08:00
}
2026-01-28 14:11:34 +08:00
versioned.StaticAccountKeys = versioned.StaticAccountKeys[:0]
2026-01-07 21:15:54 +08:00
for _, key := range msg.AccountKeys {
2026-01-28 14:11:34 +08:00
versioned.StaticAccountKeys = append(versioned.StaticAccountKeys, solana.PublicKeyFromBytes(key))
2025-12-26 10:57:37 +08:00
}
2026-01-28 14:11:34 +08:00
versioned.Instructions = versioned.Instructions[:0]
2026-01-07 21:15:54 +08:00
for _, instr := range msg.Instructions {
2026-01-28 14:11:34 +08:00
accounts := make([]uint8, 0, 16)
2026-01-07 21:15:54 +08:00
accounts = append(accounts, instr.Accounts...)
2026-01-28 14:11:34 +08:00
versioned.Instructions = append(versioned.Instructions, Instructions{
ProgramIDIndex: uint8(instr.ProgramIdIndex),
Accounts: accounts,
Data: instr.Data,
})
2025-12-26 10:57:37 +08:00
}
2026-01-28 14:11:34 +08:00
versioned.AddressTableLookups = versioned.AddressTableLookups[:0]
2026-01-07 21:15:54 +08:00
for _, lookup := range msg.AddressTableLookups {
2026-01-28 14:11:34 +08:00
writable := make([]uint8, 0, 16)
2026-01-07 21:15:54 +08:00
writable = append(writable, lookup.WritableIndexes...)
2026-01-28 14:11:34 +08:00
readonly := make([]uint8, 0, 16)
2026-01-07 21:15:54 +08:00
readonly = append(readonly, lookup.ReadonlyIndexes...)
2026-01-28 14:11:34 +08:00
versioned.AddressTableLookups = append(versioned.AddressTableLookups, AddressTableLookup{
2025-12-26 10:57:37 +08:00
AccountKey: solana.PublicKeyFromBytes(lookup.AccountKey),
WritableIndexes: writable,
ReadonlyIndexes: readonly,
2026-01-07 21:15:54 +08:00
})
2025-12-26 10:57:37 +08:00
}
2026-01-07 21:15:54 +08:00
versioned.Block = update.GetSlot()
return versioned, nil
2025-12-26 10:57:37 +08:00
}
func formatTokenAmount(amount uint64) decimal.Decimal {
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
return val.Div(decimal.NewFromInt(1_000_000))
}
func formatSolAmount(lamports uint64) decimal.Decimal {
val := decimal.NewFromBigInt(new(big.Int).SetUint64(lamports), 0)
return val.Div(decimal.NewFromInt(1_000_000_000))
}
2026-01-22 14:32:45 +08:00
func findAssociatedTokenAddressWithTokenProgram(wallet, mint, tokenProgram solana.PublicKey) (solana.PublicKey, uint8, error) {
return solana.FindProgramAddress([][]byte{
wallet[:],
tokenProgram[:],
mint[:],
}, solana.SPLAssociatedTokenAccountProgramID)
}
2025-12-30 11:03:11 +08:00
func matchMethod(data []byte, methods []byte) bool {
if len(data) < len(methods) {
return false
}
return bytes.Equal(data[0:len(methods)], methods)
}