Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77c8c0aad3 | |||
|
|
a0e46ec83e | ||
|
|
3324a71117 | ||
|
|
7557414fff | ||
|
|
eb394c5650 | ||
|
|
1223b34117 | ||
|
|
d866701679 | ||
| fa1875996c |
2
Makefile
2
Makefile
@@ -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/ ./...
|
||||||
@@ -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), "cu price:", tx.CUPrice, "cu price uint64:", tx.CUPriceUint64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = "4xkfvs5HrABpZcmbHwvqS6SRY9gYatc9DfqEZ78RCp4bgrMnmfRw4Tv8RSyT7rfDwNzmNAysezAn5TDsVBrbYXy6"
|
||||||
labelFilter = ""
|
labelFilter = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
118
pkg/shreder/program_binancewallet.go
Normal file
118
pkg/shreder/program_binancewallet.go
Normal 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
134
pkg/shreder/program_dbot.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
50
pkg/shreder/program_tradewiz.go
Normal file
50
pkg/shreder/program_tradewiz.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package shreder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tradewizProgramID = solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7")
|
||||||
|
|
||||||
|
func parseTradewizInstruction(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) < 9 {
|
||||||
|
return nil, fmt.Errorf("data too short for tradewiz buy args, len=%d", len(ix.Data))
|
||||||
|
}
|
||||||
|
if len(ix.Accounts) < 3 {
|
||||||
|
return nil, fmt.Errorf("accounts too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
// data format: 0x00 + u64(wsol amount) + u64(...)
|
||||||
|
wsolAmount := binary.LittleEndian.Uint64(ix.Data[1:9])
|
||||||
|
|
||||||
|
mint, err := tx.GetAccount(int(ix.Accounts[2]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return TxSignalBatch{&TxSignal{
|
||||||
|
TxHash: tx.Signatures[0].String(),
|
||||||
|
Label: "tradewiz",
|
||||||
|
Maker: tx.StaticAccountKeys[0].String(),
|
||||||
|
Token0Address: mint.String(),
|
||||||
|
Token1Address: wsolMint,
|
||||||
|
Token0Amount: decimal.Zero,
|
||||||
|
Token1Amount: formatSolAmount(wsolAmount),
|
||||||
|
Program: "Pump",
|
||||||
|
Event: "buy",
|
||||||
|
IsToken2022: false,
|
||||||
|
IsMayhemMode: false,
|
||||||
|
ExactSOL: true,
|
||||||
|
Block: tx.Block,
|
||||||
|
Token0AmountUint64: 0,
|
||||||
|
Token1AmountUint64: wsolAmount,
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ type TxSignal struct {
|
|||||||
IsProcessed bool `json:"is_processed"`
|
IsProcessed bool `json:"is_processed"`
|
||||||
IsToken2022 bool `json:"is_token2022"`
|
IsToken2022 bool `json:"is_token2022"`
|
||||||
IsMayhemMode bool `json:"is_mayhem_mode"`
|
IsMayhemMode bool `json:"is_mayhem_mode"`
|
||||||
TxFee decimal.Decimal `json:"tx_fee"`
|
CUPrice decimal.Decimal `json:"cu_price"`
|
||||||
|
|
||||||
ExactSOL bool `json:"exact_in"`
|
ExactSOL bool `json:"exact_in"`
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ type TxSignal struct {
|
|||||||
MaxPriceImpactBps uint16 `json:"max_price_impact_bps"`
|
MaxPriceImpactBps uint16 `json:"max_price_impact_bps"`
|
||||||
|
|
||||||
// parsed values
|
// parsed values
|
||||||
|
CUPriceUint64 uint64 `json:"-"`
|
||||||
Token0AmountUint64 uint64 `json:"-"`
|
Token0AmountUint64 uint64 `json:"-"`
|
||||||
Token1AmountUint64 uint64 `json:"-"`
|
Token1AmountUint64 uint64 `json:"-"`
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package shreder
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -28,32 +29,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 +73,8 @@ var (
|
|||||||
bonkProgramID: {parseBonkInstruction, "bonk"},
|
bonkProgramID: {parseBonkInstruction, "bonk"},
|
||||||
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
|
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
|
||||||
dlmmProgramID: {parseDlmmInstruction, "dlmm"},
|
dlmmProgramID: {parseDlmmInstruction, "dlmm"},
|
||||||
|
dbotProgramID: {parseDbotInstruction, "dbot"},
|
||||||
|
tradewizProgramID: {parseTradewizInstruction, "tradewiz"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,14 +82,21 @@ 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 (
|
||||||
|
ComputeBudgetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
|
||||||
|
VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
|
||||||
|
)
|
||||||
|
|
||||||
func FilterTransactionForEntries(versioned VersionedTransaction) bool {
|
func FilterTransactionForEntries(versioned VersionedTransaction) bool {
|
||||||
if len(versioned.Instructions) >= 1 {
|
if len(versioned.Instructions) >= 1 {
|
||||||
@@ -92,7 +109,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 +119,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 +180,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 {
|
||||||
@@ -136,13 +199,29 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cuPrice := decimal.Zero
|
||||||
|
cuPriceUint64 := uint64(0)
|
||||||
|
for _, instruction := range versioned.Instructions {
|
||||||
|
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if program.Equals(ComputeBudgetProgram) &&
|
||||||
|
len(instruction.Data) == 9 &&
|
||||||
|
instruction.Data[0] == 0x03 {
|
||||||
|
cuPriceUint64 = binary.LittleEndian.Uint64(instruction.Data[1:9])
|
||||||
|
cuPrice = formatCUPrice(cuPriceUint64)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i, instruction := range versioned.Instructions {
|
for i, instruction := range versioned.Instructions {
|
||||||
//load from address table
|
//load from address table
|
||||||
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
|
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handler, ok := parsedMap[program]
|
handler, ok := handlers[program]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -160,6 +239,8 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
|
|||||||
}
|
}
|
||||||
one.Label = handler.Label
|
one.Label = handler.Label
|
||||||
one.Block = versioned.Block
|
one.Block = versioned.Block
|
||||||
|
one.CUPrice = cuPrice
|
||||||
|
one.CUPriceUint64 = cuPriceUint64
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
@@ -173,6 +254,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")
|
||||||
@@ -224,6 +310,11 @@ func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransa
|
|||||||
return versioned, nil
|
return versioned, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatCUPrice(cuPrice uint64) decimal.Decimal {
|
||||||
|
val := decimal.NewFromBigInt(new(big.Int).SetUint64(cuPrice), 0)
|
||||||
|
return val.Div(decimal.NewFromInt(1_000_000))
|
||||||
|
}
|
||||||
|
|
||||||
func formatTokenAmount(amount uint64) decimal.Decimal {
|
func formatTokenAmount(amount uint64) decimal.Decimal {
|
||||||
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
|
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
|
||||||
return val.Div(decimal.NewFromInt(1_000_000))
|
return val.Div(decimal.NewFromInt(1_000_000))
|
||||||
|
|||||||
Reference in New Issue
Block a user