6 Commits

Author SHA1 Message Date
bijianing97
3324a71117 Update okxonchainlab parser 2026-02-02 15:39:05 +08:00
bijianing97
7557414fff Add Dbot parser 2026-02-02 11:02:10 +08:00
thloyi
eb394c5650 entries custom filter and parse 2026-01-30 12:13:31 +08:00
thloyi
1223b34117 make buidl linux-x86 2026-01-29 16:44:16 +08:00
bijianing97
d866701679 Add binanceWallet pumpfun 2026-01-29 16:40:32 +08:00
fa1875996c fix another sfng bug 2026-01-28 18:42:34 +08:00
8 changed files with 583 additions and 86 deletions

View File

@@ -19,4 +19,4 @@ shreder:
.PHONY: build
# build
build:
mkdir -p bin/ && CGO_ENABLED=0 go build -o ./bin/ ./...
mkdir -p bin/ && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ ./...

View File

@@ -25,6 +25,7 @@ func main() {
}
rpcClient := rpc.New(rpcUrl)
shreder.SetLogLevel(slog.LevelDebug)
//handlers := shreder.GetRegisteredHandlers()
shrederClient, cleanup, err := shreder.NewShrederClient(
url,
rpcClient,
@@ -55,13 +56,14 @@ func main() {
"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u",
},
},
"dflow": {
AccountRequired: []string{
"DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH",
},
},
// TODO: axiom, gmgn, etc.
}, shreder.BlocksStats(false), shreder.LogParsedStats(true), shreder.ShowTableLoaded(false))
},
//shreder.WithCustomParsers(map[solana.PublicKey]shreder.Handler{
// solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"): handlers[solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")],
// solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u"): handlers[solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")],
//}),
shreder.BlocksStats(false), shreder.LogParsedStats(true), shreder.ShowTableLoaded(false))
if err != nil {
panic(err)
}
@@ -91,8 +93,8 @@ func main() {
case <-ctx.Done():
return
case tx := <-txCh:
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" {
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart))
if tx.Label == "dbot" || tx.Label == "okxdexroutev2" {
fmt.Println("===============", tx.TxHash, tx.Label,tx.Program ,tx.Event, tx.Token0Address, tx.Token1Address,"token0amount:", tx.Token0Amount, "token1amount:",tx.Token1Amount ,"parse time:", tx.ParseEnd.Sub(tx.ParseStart))
}
}
}

View File

@@ -15,7 +15,7 @@ import (
const (
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "3hFamox2W1oWMwbRkfF5r9YiPULsdRsnR2TQsFDVtFCXf6cJ8ijGNgHGFmEbxEbVEryLg21sbt4qoGLwrPfvJ2UC"
txSignature = "4tcpUCVUZtUFDKGwZVBexkQpYiPhFgW4wfnrTEQgPrkQxpfsDEVMWN2UBfYxpy1EsDzuatuP1wgsUowTqeh2fnie"
labelFilter = ""
)

View File

@@ -8,6 +8,7 @@ import (
"runtime"
"time"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/panjf2000/ants/v2"
"google.golang.org/grpc"
@@ -23,6 +24,10 @@ type Client struct {
tableLoader *AddressTables
subscription map[string]*SubscribeRequestFilterTransactions
entriesFilter map[string]FilterParams
parser map[solana.PublicKey]Handler
pool *ants.Pool
lastSlot uint64
@@ -33,6 +38,8 @@ type ClientOpts struct {
blockStats bool
showTableLoaded bool
logParseStats bool
parser map[solana.PublicKey]Handler
}
type ClientOption func(*ClientOpts)
@@ -43,6 +50,12 @@ func ShowTableLoaded(enable bool) ClientOption {
}
}
func WithCustomParsers(parsers map[solana.PublicKey]Handler) ClientOption {
return func(opts *ClientOpts) {
opts.parser = parsers
}
}
func BlocksStats(enable bool) ClientOption {
return func(opts *ClientOpts) {
opts.blockStats = enable
@@ -82,14 +95,31 @@ func NewShrederClient(
blockStats: false,
showTableLoaded: true,
logParseStats: false,
parser: registered,
}
for _, option := range options {
option(o)
}
filterParams := make(map[string]FilterParams)
for name, params := range subscription {
filterParams[name] = FilterParams{
Exclude: parseAccountArray(params.AccountExclude),
Require: parseAccountArray(params.AccountRequired),
Include: parseAccountArray(params.AccountInclude),
}
}
if len(filterParams) == 0 {
filterParams["default"] = FilterParams{
Include: defaultFilterAccount,
}
}
s := &Client{
conn: conn,
client: NewShrederServiceClient(conn),
subscription: subscription,
entriesFilter: filterParams,
parser: o.parser,
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
pool: pool,
@@ -142,7 +172,16 @@ func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) erro
}
err = c.pool.Submit(func() {
ParseTransactionForEntries(ctx, slot, bytes.NewReader(response.Entries), c.tableLoader, txCh)
err := entriesToVersionedTransaction(slot, bytes.NewReader(response.Entries), func(versioned VersionedTransaction) {
// filter out vote transactions
if FilterTransactionForEntriesWithFilter(versioned, c.entriesFilter) {
return
}
go ParseTransactionWithHandler(ctx, versioned, c.tableLoader, txCh, c.parser)
})
if err != nil {
logger.Debug("txparser: failed to parse entries", "error", err)
}
})
if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full")
@@ -187,10 +226,15 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
}
}
txData := response.Transaction
// txData := response.Transaction
err := c.pool.Submit(func() {
ParseTransactionForSubscribe(ctx, txData, c.tableLoader, txCh, nil)
versioned, err := toVersionedTransaction(response.Transaction)
if err != nil {
logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
return
}
ParseTransactionWithHandler(ctx, versioned, c.tableLoader, txCh, c.parser)
})
if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full")
@@ -202,3 +246,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
return err
}
func parseAccountArray(accountArray []string) []solana.PublicKey {
var result []solana.PublicKey
for _, acc := range accountArray {
result = append(result, solana.MustPublicKeyFromBase58(acc))
}
return result
}

View File

@@ -0,0 +1,118 @@
package shreder
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
)
var binanceWalletProgramID = solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A")
const (
binanceWalletMinDataLen = 72
binanceWalletSolOffset = 23
binanceWalletTokenOff = 39
binanceWalletSolRepeat = 51
binanceWalletSideOff = 71
binanceWalletPumpBuy = 0x05
binanceWalletPumpSell = 0x06
)
var binanceWalletMarker = []byte{0x13, 0x2c, 0x82, 0x94, 0x48, 0x38, 0x2c, 0xee}
func parseBinanceWalletInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) < len(binanceWalletMarker) || !bytes.Contains(instruction.Data, binanceWalletMarker) {
return nil, nil
}
if len(instruction.Data) < binanceWalletMinDataLen {
return nil, fmt.Errorf("data too short for binance wallet, len=%d", len(instruction.Data))
}
side := instruction.Data[binanceWalletSideOff]
if side != binanceWalletPumpBuy && side != binanceWalletPumpSell {
return nil, nil
}
if len(instruction.Accounts) <= 8 {
return nil, fmt.Errorf("accounts too short")
}
wsolIdx := 7
tokenIdx := 8
if side == binanceWalletPumpSell {
wsolIdx = 8
tokenIdx = 7
}
wsolKey, err := tx.GetAccount(int(instruction.Accounts[wsolIdx]))
if err != nil {
return nil, err
}
if !wsolKey.Equals(solana.WrappedSol) {
return nil, nil
}
mint, err := tx.GetAccount(int(instruction.Accounts[tokenIdx]))
if err != nil {
return nil, err
}
amountA := binary.LittleEndian.Uint64(instruction.Data[binanceWalletSolOffset : binanceWalletSolOffset+8])
if amountA == 0 && len(instruction.Data) >= binanceWalletSolRepeat+8 {
repeat := binary.LittleEndian.Uint64(instruction.Data[binanceWalletSolRepeat : binanceWalletSolRepeat+8])
if repeat > 0 {
amountA = repeat
}
}
amountB := binary.LittleEndian.Uint64(instruction.Data[binanceWalletTokenOff : binanceWalletTokenOff+8])
solAmount := amountA
tokenAmount := amountB
if side == binanceWalletPumpSell {
solAmount = amountB
tokenAmount = amountA
}
maker := ""
if len(tx.StaticAccountKeys) > 0 {
maker = tx.StaticAccountKeys[0].String()
} else if len(instruction.Accounts) > 0 {
key, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
maker = key.String()
}
event := "buy"
exactIn := true
if side == binanceWalletPumpSell {
event = "sell"
exactIn = false
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "binancewallet",
Maker: maker,
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: event,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: exactIn,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}}, nil
}

134
pkg/shreder/program_dbot.go Normal file
View File

@@ -0,0 +1,134 @@
package shreder
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
var dbotProgramID = solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs")
var (
dbotPumpFunBuyIX = []byte{0x4e, 0x13, 0x6d, 0x72, 0x3d, 0x72, 0xbe, 0x9d}
dbotPumpAmmBuyIX = []byte{0x99, 0x76, 0xb6, 0x1e, 0xe4, 0x03, 0xdc, 0xf4}
)
func parseDbotInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
ix := tx.Instructions[instructionIndex]
if len(ix.Data) < 8 {
return nil, nil
}
isPumpFun := false
isPumpAmm := false
if len(ix.Accounts) > 11 {
key, err := tx.GetAccount(int(ix.Accounts[11]))
if err != nil {
return nil, err
}
if key.Equals(pumpProgramID) {
isPumpFun = true
}
}
if len(ix.Accounts) > 16 {
key, err := tx.GetAccount(int(ix.Accounts[16]))
if err != nil {
return nil, err
}
if key.Equals(pumpAmmProgramID) {
isPumpAmm = true
}
}
disc := ix.Data[:8]
if isPumpFun {
if !bytes.Equal(disc, dbotPumpFunBuyIX) {
return nil, nil
}
if len(ix.Data) < 16 {
return nil, fmt.Errorf("data too short for dbot pumpfun buy args, len=%d", len(ix.Data))
}
if len(ix.Accounts) < 3 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(ix.Accounts[2]))
if err != nil {
return nil, err
}
solAmount := binary.LittleEndian.Uint64(ix.Data[8:16])
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "dbot",
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
ExactSOL: true,
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: solAmount,
}}, nil
}
if isPumpAmm {
if !bytes.Equal(disc, dbotPumpAmmBuyIX) {
return nil, nil
}
if len(ix.Data) < 16 {
return nil, fmt.Errorf("data too short for dbot pumpamm buy args, len=%d", len(ix.Data))
}
if len(ix.Accounts) < 5 {
return nil, fmt.Errorf("accounts too short")
}
base, err := tx.GetAccount(int(ix.Accounts[3]))
if err != nil {
return nil, err
}
quote, err := tx.GetAccount(int(ix.Accounts[4]))
if err != nil {
return nil, err
}
if !quote.Equals(solana.WrappedSol) {
return nil, nil
}
solAmount := binary.LittleEndian.Uint64(ix.Data[8:16])
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "dbot",
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(solAmount),
Program: "PumpAMM",
Event: "buy",
ExactSOL: true,
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: solAmount,
}}, nil
}
return nil, nil
}

View File

@@ -291,69 +291,189 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
}
var (
inputAmount uint64
routeCount int
pumpAmmSellAmount uint64
pumpAmmBuyAmount uint64
pumpFunSellAmount uint64
pumpFunBuyAmount uint64
pumpAmmSellCount int
pumpAmmBuyCount int
pumpFunSellCount int
pumpFunBuyCount int
)
for _, route := range args.Routes {
if route.Index == 1 && (route.Dex == OKCV2_PumpfunammSell ||
route.Dex == OKCV2_PumpfunSell2) {
routeCount++
inputAmount = args.AmountIn * uint64(route.Weight) / 10000
if route.Index != 1 {
continue
}
switch route.Dex {
case OKCV2_PumpfunammSell:
pumpAmmSellCount++
pumpAmmSellAmount = args.AmountIn * uint64(route.Weight) / 10000
case OKCV2_PumpfunammBuy:
pumpAmmBuyCount++
pumpAmmBuyAmount = args.AmountIn * uint64(route.Weight) / 10000
case OKCV2_PumpfunSell2:
pumpFunSellCount++
pumpFunSellAmount = args.AmountIn * uint64(route.Weight) / 10000
case OKCV2_PumpfunBuy2:
pumpFunBuyCount++
pumpFunBuyAmount = args.AmountIn * uint64(route.Weight) / 10000
}
}
if routeCount > 1 {
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", routeCount)
if pumpAmmSellCount > 1 {
logger.Warn("pumpAmmSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmSellCount)
return nil, nil
}
if inputAmount == 0 {
if pumpAmmBuyCount > 1 {
logger.Warn("pumpAmmSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmBuyCount)
return nil, nil
}
if pumpFunSellCount > 1 {
logger.Warn("pumpFunSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpFunSellCount)
return nil, nil
}
if pumpFunBuyCount > 1 {
logger.Warn("pumpFunSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpFunBuyCount)
return nil, nil
}
if pumpAmmSellAmount == 0 && pumpAmmBuyAmount == 0 && pumpFunSellAmount == 0 && pumpFunBuyAmount == 0 {
return nil, nil
}
srcMint, err := tx.GetAccount(int(ix.Accounts[3]))
out := make(TxSignalBatch, 0, 2)
var (
srcIdx uint8
)
if pumpFunBuyAmount > 0 || pumpFunSellAmount > 0 {
if pumpFunBuyAmount > 0 {
if len(ix.Accounts) < 5 {
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
}
baseMint, err := tx.GetAccount(int(ix.Accounts[4]))
if err != nil {
return nil, err
}
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(pumpFunBuyAmount),
Event: "buy",
Program: "Pump",
IsProcessed: false,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Token0AmountUint64: 0,
Token1AmountUint64: pumpFunBuyAmount,
})
}
if pumpFunSellAmount > 0 {
if len(ix.Accounts) < 4 {
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
}
baseMint, err := tx.GetAccount(int(ix.Accounts[3]))
if err != nil {
return nil, err
}
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(pumpFunSellAmount),
Token1Amount: decimal.Zero,
Event: "sell",
Program: "Pump",
IsProcessed: false,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Token0AmountUint64: pumpFunSellAmount,
Token1AmountUint64: 0,
})
}
}
if pumpAmmBuyAmount > 0 || pumpAmmSellAmount > 0 {
if len(ix.Accounts) <= 15 {
if len(out) == 0 {
return nil, nil
}
return out, nil
}
accounts := ix.Accounts[14:]
var pumpAmmIdx uint8
for i, acctIdx := range accounts {
key, err := tx.GetAccount(int(acctIdx))
if err != nil {
return nil, err
}
if key.Equals(pumpAmmProgramID) {
srcIdx = uint8(i + 6)
pumpAmmIdx = uint8(i + 6)
break
}
}
if srcIdx == 0 || int(srcIdx+1) >= len(accounts) {
if pumpAmmIdx == 0 || int(pumpAmmIdx+1) >= len(accounts) {
if len(out) == 0 {
return nil, nil
}
baseMint, err := tx.GetAccount(int(accounts[srcIdx]))
return out, nil
}
baseMint, err := tx.GetAccount(int(accounts[pumpAmmIdx]))
if err != nil {
return nil, err
}
if !baseMint.Equals(srcMint) {
return nil, nil
}
quoteMint, err := tx.GetAccount(int(accounts[srcIdx+1]))
quoteMint, err := tx.GetAccount(int(accounts[pumpAmmIdx+1]))
if err != nil {
return nil, err
}
if !quoteMint.Equals(solana.WrappedSol) {
if len(out) == 0 {
return nil, nil
}
return TxSignalBatch{&TxSignal{
return out, nil
}
if pumpAmmBuyAmount > 0 {
if len(ix.Accounts) < 5 {
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
}
srcMint, err := tx.GetAccount(int(ix.Accounts[4]))
if err != nil {
return nil, err
}
if baseMint.Equals(srcMint) {
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(inputAmount),
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(pumpAmmBuyAmount),
Event: "buy",
Program: "PumpAMM",
IsProcessed: false,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Token0AmountUint64: 0,
Token1AmountUint64: pumpAmmBuyAmount,
})
}
} else if pumpAmmSellAmount > 0 {
if len(ix.Accounts) < 4 {
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
}
srcMint, err := tx.GetAccount(int(ix.Accounts[3]))
if err != nil {
return nil, err
}
if baseMint.Equals(srcMint) {
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(pumpAmmSellAmount),
Token1Amount: decimal.Zero,
Event: "sell",
Program: "PumpAMM",
@@ -361,7 +481,15 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Token0AmountUint64: inputAmount,
Token0AmountUint64: pumpAmmSellAmount,
Token1AmountUint64: 0,
}}, nil
})
}
}
}
if len(out) == 0 {
return nil, nil
}
return out, nil
}

View File

@@ -28,32 +28,39 @@ type FillAccount interface {
}
func init() {
for account := range parsedMap {
parseProgram = append(parseProgram, account)
for account := range registered {
defaultFilterAccount = append(defaultFilterAccount, account)
}
//"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
//"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
//"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
parseProgram = append(parseProgram,
defaultFilterAccount = append(defaultFilterAccount,
solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"),
solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"),
solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
)
slices.SortFunc(parseProgram, func(a, b solana.PublicKey) int {
slices.SortFunc(defaultFilterAccount, func(a, b solana.PublicKey) int {
return bytes.Compare(a[:], b[:])
})
}
var (
parseProgram []solana.PublicKey
type FilterParams struct {
Require []solana.PublicKey
Include []solana.PublicKey
Exclude []solana.PublicKey
}
parsedMap = map[solana.PublicKey]Handler{
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"},
@@ -65,6 +72,7 @@ var (
bonkProgramID: {parseBonkInstruction, "bonk"},
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
dlmmProgramID: {parseDlmmInstruction, "dlmm"},
dbotProgramID: {parseDbotInstruction, "dbot"},
}
)
@@ -72,12 +80,16 @@ func ParseTransactionForSubscribe(ctx context.Context, update *SubscribeUpdateTr
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")
@@ -92,7 +104,7 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
// accounts filter?
include := false
for _, key := range versioned.StaticAccountKeys {
_, include = slices.BinarySearchFunc(parseProgram, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
_, include = slices.BinarySearchFunc(defaultFilterAccount, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
return bytes.Compare(key[:], key2[:])
})
if include {
@@ -102,6 +114,53 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
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
@@ -116,8 +175,7 @@ func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader
}
}
func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) {
// staticKeys := versioned.Message.StaticAccountKeys
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 {
@@ -142,7 +200,7 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
if err != nil {
continue
}
handler, ok := parsedMap[program]
handler, ok := handlers[program]
if !ok {
continue
}
@@ -173,6 +231,11 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
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")