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 .PHONY: build
# build # 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) rpcClient := rpc.New(rpcUrl)
shreder.SetLogLevel(slog.LevelDebug) shreder.SetLogLevel(slog.LevelDebug)
//handlers := shreder.GetRegisteredHandlers()
shrederClient, cleanup, err := shreder.NewShrederClient( shrederClient, cleanup, err := shreder.NewShrederClient(
url, url,
rpcClient, rpcClient,
@@ -55,13 +56,14 @@ func main() {
"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u", "proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u",
}, },
}, },
"dflow": {
AccountRequired: []string{
"DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH",
},
},
// TODO: axiom, gmgn, etc. // 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 { if err != nil {
panic(err) panic(err)
} }
@@ -91,8 +93,8 @@ func main() {
case <-ctx.Done(): case <-ctx.Done():
return return
case tx := <-txCh: case tx := <-txCh:
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" { if tx.Label == "dbot" || tx.Label == "okxdexroutev2" {
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart)) 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 ( const (
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d" rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "3hFamox2W1oWMwbRkfF5r9YiPULsdRsnR2TQsFDVtFCXf6cJ8ijGNgHGFmEbxEbVEryLg21sbt4qoGLwrPfvJ2UC" txSignature = "4tcpUCVUZtUFDKGwZVBexkQpYiPhFgW4wfnrTEQgPrkQxpfsDEVMWN2UBfYxpy1EsDzuatuP1wgsUowTqeh2fnie"
labelFilter = "" labelFilter = ""
) )

View File

@@ -8,6 +8,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -23,6 +24,10 @@ type Client struct {
tableLoader *AddressTables tableLoader *AddressTables
subscription map[string]*SubscribeRequestFilterTransactions subscription map[string]*SubscribeRequestFilterTransactions
entriesFilter map[string]FilterParams
parser map[solana.PublicKey]Handler
pool *ants.Pool pool *ants.Pool
lastSlot uint64 lastSlot uint64
@@ -33,6 +38,8 @@ type ClientOpts struct {
blockStats bool blockStats bool
showTableLoaded bool showTableLoaded bool
logParseStats bool logParseStats bool
parser map[solana.PublicKey]Handler
} }
type ClientOption func(*ClientOpts) 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 { func BlocksStats(enable bool) ClientOption {
return func(opts *ClientOpts) { return func(opts *ClientOpts) {
opts.blockStats = enable opts.blockStats = enable
@@ -82,14 +95,31 @@ func NewShrederClient(
blockStats: false, blockStats: false,
showTableLoaded: true, showTableLoaded: true,
logParseStats: false, logParseStats: false,
parser: registered,
} }
for _, option := range options { for _, option := range options {
option(o) 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{ s := &Client{
conn: conn, conn: conn,
client: NewShrederServiceClient(conn), client: NewShrederServiceClient(conn),
subscription: subscription, subscription: subscription,
entriesFilter: filterParams,
parser: o.parser,
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded), tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
pool: pool, pool: pool,
@@ -142,7 +172,16 @@ func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) erro
} }
err = c.pool.Submit(func() { 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) { if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full") 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() { 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) { if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full") logger.Warn("task pool is full")
@@ -202,3 +246,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
return err 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)) return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
} }
var ( var (
inputAmount uint64 pumpAmmSellAmount uint64
routeCount int pumpAmmBuyAmount uint64
pumpFunSellAmount uint64
pumpFunBuyAmount uint64
pumpAmmSellCount int
pumpAmmBuyCount int
pumpFunSellCount int
pumpFunBuyCount int
) )
for _, route := range args.Routes { for _, route := range args.Routes {
if route.Index == 1 && (route.Dex == OKCV2_PumpfunammSell || if route.Index != 1 {
route.Dex == OKCV2_PumpfunSell2) { continue
routeCount++ }
inputAmount = args.AmountIn * uint64(route.Weight) / 10000 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 { if pumpAmmSellCount > 1 {
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", routeCount) logger.Warn("pumpAmmSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmSellCount)
return nil, nil 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 return nil, nil
} }
srcMint, err := tx.GetAccount(int(ix.Accounts[3])) out := make(TxSignalBatch, 0, 2)
var ( if pumpFunBuyAmount > 0 || pumpFunSellAmount > 0 {
srcIdx uint8 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(ix.Accounts) <= 15 {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
}
accounts := ix.Accounts[14:] accounts := ix.Accounts[14:]
var pumpAmmIdx uint8
for i, acctIdx := range accounts { for i, acctIdx := range accounts {
key, err := tx.GetAccount(int(acctIdx)) key, err := tx.GetAccount(int(acctIdx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if key.Equals(pumpAmmProgramID) { if key.Equals(pumpAmmProgramID) {
srcIdx = uint8(i + 6) pumpAmmIdx = uint8(i + 6)
break break
} }
} }
if srcIdx == 0 || int(srcIdx+1) >= len(accounts) { if pumpAmmIdx == 0 || int(pumpAmmIdx+1) >= len(accounts) {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
baseMint, err := tx.GetAccount(int(accounts[srcIdx])) }
baseMint, err := tx.GetAccount(int(accounts[pumpAmmIdx]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !baseMint.Equals(srcMint) { quoteMint, err := tx.GetAccount(int(accounts[pumpAmmIdx+1]))
return nil, nil
}
quoteMint, err := tx.GetAccount(int(accounts[srcIdx+1]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !quoteMint.Equals(solana.WrappedSol) { if !quoteMint.Equals(solana.WrappedSol) {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
return TxSignalBatch{&TxSignal{ }
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(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(), Token0Address: baseMint.String(),
Token1Address: wsolMint, 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, Token1Amount: decimal.Zero,
Event: "sell", Event: "sell",
Program: "PumpAMM", Program: "PumpAMM",
@@ -361,7 +481,15 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
IsToken2022: false, IsToken2022: false,
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: false, ExactSOL: false,
Token0AmountUint64: inputAmount, Token0AmountUint64: pumpAmmSellAmount,
Token1AmountUint64: 0, Token1AmountUint64: 0,
}}, nil })
}
}
}
if len(out) == 0 {
return nil, nil
}
return out, nil
} }

View File

@@ -28,32 +28,39 @@ type FillAccount interface {
} }
func init() { func init() {
for account := range parsedMap { for account := range registered {
parseProgram = append(parseProgram, account) defaultFilterAccount = append(defaultFilterAccount, account)
} }
//"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority //"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
//"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config //"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
//"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program //"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
parseProgram = append(parseProgram, defaultFilterAccount = append(defaultFilterAccount,
solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"), solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"),
solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"), solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"),
solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"), 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[:]) return bytes.Compare(a[:], b[:])
}) })
} }
var ( type FilterParams struct {
parseProgram []solana.PublicKey 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"}, pumpProgramID: {parsePumpInstruction, "pump"},
azczProgramID: {parseAzczInstruction, "azcz"}, azczProgramID: {parseAzczInstruction, "azcz"},
f5tfProgramID: {parseF5tfInstruction, "f5tf"}, f5tfProgramID: {parseF5tfInstruction, "f5tf"},
flasProgramID: {parseFlasInstruction, "flas"}, flasProgramID: {parseFlasInstruction, "flas"},
photonProgramID: {parsePhotonInstruction, "photon"}, photonProgramID: {parsePhotonInstruction, "photon"},
pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"}, pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"},
binanceWalletProgramID: {parseBinanceWalletInstruction, "binancewallet"},
boboProgramID: {parseBoboInstruction, "bobo"}, boboProgramID: {parseBoboInstruction, "bobo"},
qtkvProgramID: {parseQtkvInstruction, "qtkv"}, qtkvProgramID: {parseQtkvInstruction, "qtkv"},
fjszProgramID: {parseFjszInstruction, "fjsz"}, fjszProgramID: {parseFjszInstruction, "fjsz"},
@@ -65,6 +72,7 @@ var (
bonkProgramID: {parseBonkInstruction, "bonk"}, bonkProgramID: {parseBonkInstruction, "bonk"},
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"}, bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
dlmmProgramID: {parseDlmmInstruction, "dlmm"}, dlmmProgramID: {parseDlmmInstruction, "dlmm"},
dbotProgramID: {parseDbotInstruction, "dbot"},
} }
) )
@@ -72,12 +80,16 @@ func ParseTransactionForSubscribe(ctx context.Context, update *SubscribeUpdateTr
versioned, err := toVersionedTransaction(update) versioned, err := toVersionedTransaction(update)
if err != nil { if err != nil {
logger.Debug("txparser: failed to convert to versioned transaction", "error", err) logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
if done != nil {
close(done) close(done)
}
return return
} }
ParseTransaction(ctx, versioned, loader, parsed) ParseTransaction(ctx, versioned, loader, parsed)
if done != nil {
close(done) close(done)
} }
}
var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111") var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
@@ -92,7 +104,7 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
// accounts filter? // accounts filter?
include := false include := false
for _, key := range versioned.StaticAccountKeys { 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[:]) return bytes.Compare(key[:], key2[:])
}) })
if include { if include {
@@ -102,6 +114,53 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
return !include 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) { func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader io.Reader, loader *AddressTables, parsed chan<- TxSignal) {
err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) { err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) {
// filter out vote transactions // 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) { func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) {
// staticKeys := versioned.Message.StaticAccountKeys
if loader != nil && len(versioned.AddressTableLookups) > 0 { if loader != nil && len(versioned.AddressTableLookups) > 0 {
lookupTableOk := true lookupTableOk := true
for _, lookups := range versioned.AddressTableLookups { for _, lookups := range versioned.AddressTableLookups {
@@ -142,7 +200,7 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
if err != nil { if err != nil {
continue continue
} }
handler, ok := parsedMap[program] handler, ok := handlers[program]
if !ok { if !ok {
continue continue
} }
@@ -173,6 +231,11 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
return 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) { func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransaction, error) {
if update == nil || update.Transaction == nil || update.Transaction.Message == nil { if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
return VersionedTransaction{}, fmt.Errorf("transaction is nil") return VersionedTransaction{}, fmt.Errorf("transaction is nil")