From 21eba4264fc581c992be135938931ce9a630f899 Mon Sep 17 00:00:00 2001 From: samlior Date: Mon, 23 Mar 2026 15:43:08 +0800 Subject: [PATCH] chore: support pump create --- pkg/shreder/program_pump.go | 24 ++++++- pkg/shreder/tx.go | 43 ++++++------- pkg/shreder/txparser_test.go | 119 +++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 23 deletions(-) diff --git a/pkg/shreder/program_pump.go b/pkg/shreder/program_pump.go index 580b256..ea5f7f7 100644 --- a/pkg/shreder/program_pump.go +++ b/pkg/shreder/program_pump.go @@ -29,6 +29,15 @@ type pumpBuyArgs struct { } type pumpCreateCoinV2Args struct { + Name string + Symbol string + Uri string + Creator solana.PublicKey + IsMayhemMode bool + IsCashbackEnabled bool +} + +type legacyPumpCreateCoinV2Args struct { Name string Symbol string Uri string @@ -37,7 +46,6 @@ type pumpCreateCoinV2Args struct { } func parsePumpInstruction(msg VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { - instruction := msg.Instructions[instructionIndex] if len(instruction.Data) < 8 { return nil, fmt.Errorf("data is empty") @@ -114,7 +122,18 @@ func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSi var args pumpCreateCoinV2Args if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { - return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err) + var legacyArgs legacyPumpCreateCoinV2Args + if err := borsh.Deserialize(&legacyArgs, instruction.Data[8:]); err != nil { + return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err) + } + args = pumpCreateCoinV2Args{ + Name: legacyArgs.Name, + Symbol: legacyArgs.Symbol, + Uri: legacyArgs.Uri, + Creator: legacyArgs.Creator, + IsMayhemMode: legacyArgs.IsMayhemMode, + IsCashbackEnabled: false, + } } return &TxSignal{ @@ -129,6 +148,7 @@ func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSi Event: "create", IsToken2022: tokenProgramKey.String() != tokenProgram, IsMayhemMode: args.IsMayhemMode, + IsCashbackEnabled: args.IsCashbackEnabled, Block: tx.Block, Token0AmountUint64: 0, Token1AmountUint64: 0, diff --git a/pkg/shreder/tx.go b/pkg/shreder/tx.go index 42577cd..dced881 100644 --- a/pkg/shreder/tx.go +++ b/pkg/shreder/tx.go @@ -28,27 +28,28 @@ func SetLogLevel(level slog.Level) { } type TxSignal struct { - Source string `json:"source"` - Label string `json:"label"` - TxHash string `json:"tx_hash"` - Maker string `json:"maker"` - Token0Address string `json:"token0_address"` - Token1Address string `json:"token1_address"` - Token0Amount decimal.Decimal `json:"token0_amount"` - Token1Amount decimal.Decimal `json:"token1_amount"` - Block uint64 `json:"block"` - BlockAt time.Time `json:"block_at"` - BlockIndex uint64 `json:"block_index"` - Event string `json:"event"` - Program string `json:"program"` - IsProcessed bool `json:"is_processed"` - IsToken2022 bool `json:"is_token2022"` - IsMayhemMode bool `json:"is_mayhem_mode"` - CUPrice decimal.Decimal `json:"cu_price"` - CULimit uint32 `json:"cu_limit"` - SWQoSAgent string `json:"swqos_agent"` - SWQoSTips decimal.Decimal `json:"swqos_tips"` - TableCnt int `json:"table_cnt"` + Source string `json:"source"` + Label string `json:"label"` + TxHash string `json:"tx_hash"` + Maker string `json:"maker"` + Token0Address string `json:"token0_address"` + Token1Address string `json:"token1_address"` + Token0Amount decimal.Decimal `json:"token0_amount"` + Token1Amount decimal.Decimal `json:"token1_amount"` + Block uint64 `json:"block"` + BlockAt time.Time `json:"block_at"` + BlockIndex uint64 `json:"block_index"` + Event string `json:"event"` + Program string `json:"program"` + IsProcessed bool `json:"is_processed"` + IsToken2022 bool `json:"is_token2022"` + IsMayhemMode bool `json:"is_mayhem_mode"` + IsCashbackEnabled bool `json:"is_cashback_enabled"` + CUPrice decimal.Decimal `json:"cu_price"` + CULimit uint32 `json:"cu_limit"` + SWQoSAgent string `json:"swqos_agent"` + SWQoSTips decimal.Decimal `json:"swqos_tips"` + TableCnt int `json:"table_cnt"` ExactSOL bool `json:"exact_in"` diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index dfbb2a2..25bd8ba 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -777,3 +777,122 @@ func TestParseRaydiumLaunchLabCreate2(t *testing.T) { t.Fatalf("expected IsToken2022 true, got false") } } + +func manuallyLoadAddressTable(t *testing.T, client *rpc.Client, tablePubkey solana.PublicKey) *AddressTables { + loader := NewAddressTables(client, false) + table, err := loader.loadAddressTable(tablePubkey) + if err != nil { + t.Fatalf("failed to load address table: %s", err) + } + + loader.tables.Add(tablePubkey, &TableInfo{ + addresses: table, + }) + + return loader +} + +func TestParsePumpCreate(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("AXVvmhWaaPtV52jqYuTNqp1xRrkbxhfJfeHQKxq5cbvZ")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "3vUAHpBUoxeoZheo9m3XmufNUWmJWRAN4xZjSqDos71GL6tCKSTmJV6YeMS5XdVAbRAfqQi1rPusjbmEhoam4x9Y"), + loader, + ch, + closed, + ) + }() + go func() { + <-closed + close(ch) + }() + signals := make([]TxSignal, 0) + for signal := range ch { + signals = append(signals, signal) + } + + if len(signals) == 0 { + t.Fatalf("expected at least 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "pump" { + t.Fatalf("expected pump signal, got %s", signal.Label) + } + if signal.Event != "create" { + t.Fatalf("expected create event, got %s", signal.Event) + } + if signal.Maker != "8oVJLE69qH1i5jotFABcCoaPS38DAyZ7djRg6uuD3Cb7" { + t.Fatalf("expected maker 8oVJLE69qH1i5jotFABcCoaPS38DAyZ7djRg6uuD3Cb7, got %s", signal.Maker) + } + if signal.Token0Address != "AtaWsdyfcANLsHNdpYJyFdNvwSQnUzvhRN2MCCP9pump" { + t.Fatalf("expected token0 address AtaWsdyfcANLsHNdpYJyFdNvwSQnUzvhRN2MCCP9pump, got %s", signal.Token0Address) + } + if !signal.IsToken2022 { + t.Fatalf("expected IsToken2022 true, got false") + } + if signal.IsCashbackEnabled { + t.Fatalf("expected IsCashbackEnabled true, got false") + } +} + +func TestParsePumpCreate2(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "3EKKjtNFzhtQaA9GyPt5UJHLr5mWT2XodaxWoenUrcaPpN8BKm84ATVapdUJcb9sJVFyS4iKD9spGBKfqkSFutea"), + nil, + ch, + closed, + ) + }() + go func() { + <-closed + close(ch) + }() + signals := make([]TxSignal, 0) + for signal := range ch { + signals = append(signals, signal) + } + + if len(signals) == 0 { + t.Fatalf("expected at least 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "pump" { + t.Fatalf("expected pump signal, got %s", signal.Label) + } + if signal.Event != "create" { + t.Fatalf("expected create event, got %s", signal.Event) + } + if signal.Maker != "5mGqXMqUbJzxQ9aqbBttB4JUfAMqceSTmrtpt6RuPXdC" { + t.Fatalf("expected maker 5mGqXMqUbJzxQ9aqbBttB4JUfAMqceSTmrtpt6RuPXdC, got %s", signal.Maker) + } + if signal.Token0Address != "J9Hqi5VddcTNJ2F5EyxSZxC2JpJeymfJXjLhsCVPZpna" { + t.Fatalf("expected token0 address J9Hqi5VddcTNJ2F5EyxSZxC2JpJeymfJXjLhsCVPZpna, got %s", signal.Token0Address) + } + if !signal.IsToken2022 { + t.Fatalf("expected IsToken2022 true, got false") + } + if signal.IsCashbackEnabled { + t.Fatalf("expected IsCashbackEnabled true, got false") + } +}