package shreder import ( "bytes" "context" "fmt" "io" "math/big" "slices" "strings" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) const ( wsolMint = "So11111111111111111111111111111111111111112" tokenProgram = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ) type Handler struct { Func func(tx VersionedTransaction, idx int) (TxSignalBatch, error) Label string } type FillAccount interface { FillAccount(account solana.PublicKey) } func init() { for account := range registered { defaultFilterAccount = append(defaultFilterAccount, account) } //"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority //"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config //"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program defaultFilterAccount = append(defaultFilterAccount, solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"), solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"), solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"), ) slices.SortFunc(defaultFilterAccount, func(a, b solana.PublicKey) int { return bytes.Compare(a[:], b[:]) }) } type FilterParams struct { Require []solana.PublicKey Include []solana.PublicKey Exclude []solana.PublicKey } var ( defaultFilterAccount []solana.PublicKey registered = map[solana.PublicKey]Handler{ pumpProgramID: {parsePumpInstruction, "pump"}, azczProgramID: {parseAzczInstruction, "azcz"}, f5tfProgramID: {parseF5tfInstruction, "f5tf"}, flasProgramID: {parseFlasInstruction, "flas"}, photonProgramID: {parsePhotonInstruction, "photon"}, pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"}, binanceWalletProgramID: {parseBinanceWalletInstruction, "binancewallet"}, 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"}, dbotProgramID: {parseDbotInstruction, "dbot"}, tradewizProgramID: {parseTradewizInstruction, "tradewiz"}, } ) 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) if done != nil { close(done) } return } ParseTransaction(ctx, versioned, loader, parsed) if done != nil { close(done) } } 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 } } // accounts filter? include := false for _, key := range versioned.StaticAccountKeys { _, include = slices.BinarySearchFunc(defaultFilterAccount, key, func(key solana.PublicKey, key2 solana.PublicKey) int { return bytes.Compare(key[:], key2[:]) }) if include { break } } return !include } 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 } 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) return } } func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) { if loader != nil && len(versioned.AddressTableLookups) > 0 { lookupTableOk := true for _, lookups := range versioned.AddressTableLookups { lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.WritableIndexes) if !lookupTableOk { break } } if lookupTableOk { for _, lookups := range versioned.AddressTableLookups { lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.ReadonlyIndexes) if !lookupTableOk { break } } } } for i, instruction := range versioned.Instructions { //load from address table program, err := versioned.GetAccount(int(instruction.ProgramIDIndex)) if err != nil { continue } handler, ok := handlers[program] if !ok { continue } 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()) } continue } 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: } } } } return } func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) { // staticKeys := versioned.Message.StaticAccountKeys ParseTransactionWithHandler(ctx, versioned, loader, parsed, registered) } func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransaction, error) { if update == nil || update.Transaction == nil || update.Transaction.Message == nil { return VersionedTransaction{}, fmt.Errorf("transaction is nil") } protoTx := update.Transaction msg := protoTx.Message versioned := VersionedTransaction{ Signatures: make([]solana.Signature, 0, 10), StaticAccountKeys: make([]solana.PublicKey, 0, 256), Instructions: make([]Instructions, 0, 16), AddressTableLookups: make([]AddressTableLookup, 0, 10), } for _, rawSig := range protoTx.Signatures { versioned.Signatures = append(versioned.Signatures, solana.SignatureFromBytes(rawSig)) } versioned.StaticAccountKeys = versioned.StaticAccountKeys[:0] for _, key := range msg.AccountKeys { versioned.StaticAccountKeys = append(versioned.StaticAccountKeys, solana.PublicKeyFromBytes(key)) } versioned.Instructions = versioned.Instructions[:0] for _, instr := range msg.Instructions { accounts := make([]uint8, 0, 16) accounts = append(accounts, instr.Accounts...) versioned.Instructions = append(versioned.Instructions, Instructions{ ProgramIDIndex: uint8(instr.ProgramIdIndex), Accounts: accounts, Data: instr.Data, }) } versioned.AddressTableLookups = versioned.AddressTableLookups[:0] for _, lookup := range msg.AddressTableLookups { writable := make([]uint8, 0, 16) writable = append(writable, lookup.WritableIndexes...) readonly := make([]uint8, 0, 16) readonly = append(readonly, lookup.ReadonlyIndexes...) versioned.AddressTableLookups = append(versioned.AddressTableLookups, AddressTableLookup{ AccountKey: solana.PublicKeyFromBytes(lookup.AccountKey), WritableIndexes: writable, ReadonlyIndexes: readonly, }) } versioned.Block = update.GetSlot() return versioned, nil } 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)) } func findAssociatedTokenAddressWithTokenProgram(wallet, mint, tokenProgram solana.PublicKey) (solana.PublicKey, uint8, error) { return solana.FindProgramAddress([][]byte{ wallet[:], tokenProgram[:], mint[:], }, solana.SPLAssociatedTokenAccountProgramID) } func matchMethod(data []byte, methods []byte) bool { if len(data) < len(methods) { return false } return bytes.Equal(data[0:len(methods)], methods) }