package shreder import ( "encoding/binary" "fmt" "github.com/gagliardetto/solana-go" "github.com/near/borsh-go" "github.com/shopspring/decimal" ) var pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") var ( pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119} pumpCreateCoinV2IX = []byte{214, 144, 76, 236, 95, 139, 49, 180} pumpExtendedSellIX = []byte{51, 230, 133, 164, 1, 127, 131, 173} pumpBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234} pumpBuyV2TokensIX = []byte{56, 252, 116, 8, 158, 223, 205, 95} ) type pumpExtendedSellArgs struct { Amount uint64 MinSolOutput uint64 } type pumpBuyArgs struct { Amount uint64 MaxSolCost uint64 } type pumpCreateCoinV2Args struct { Name string Symbol string Uri string Creator solana.PublicKey IsMayhemMode bool } func parsePumpInstruction(msg VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { instruction := msg.Instructions[instructionIndex] if len(instruction.Data) < 8 { return nil, fmt.Errorf("data is empty") } var ( err error txSignal *TxSignal ) if matchMethod(instruction.Data[0:8], pumpBuyV2TokensIX) || matchMethod(instruction.Data[0:8], pumpBuyTokensIX) { txSignal, err = parsePumpBuy(msg, instruction) } else if matchMethod(instruction.Data[0:8], pumpExtendedSellIX) { txSignal, err = parsePumpSell(msg, instruction) } else if matchMethod(instruction.Data[0:8], pumpCreateCoinIX) { txSignal, err = parsePumpCreate(msg, instruction) } else if matchMethod(instruction.Data[0:8], pumpCreateCoinV2IX) { txSignal, err = parsePumpCreateV2(msg, instruction) } if txSignal != nil { return TxSignalBatch{txSignal}, err } return nil, err } func parsePumpCreate(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } mint, err := tx.GetAccount(int(instruction.Accounts[0])) if err != nil { return nil, err } creator, err := tx.GetAccount(int(instruction.Accounts[7])) if err != nil { return nil, err } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pump", Maker: creator.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: decimal.Zero, Token1Amount: decimal.Zero, Program: "Pump", Event: "create", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: 0, Token1AmountUint64: 0, }, nil } func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 8 { return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data)) } mint, err := tx.GetAccount(int(instruction.Accounts[0])) if err != nil { return nil, err } tokenProgramKey, err := tx.GetAccount(int(instruction.Accounts[7])) if err != nil { return nil, err } 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) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pump", Maker: args.Creator.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: decimal.Zero, Token1Amount: decimal.Zero, Program: "Pump", Event: "create", IsToken2022: tokenProgramKey.String() != tokenProgram, IsMayhemMode: args.IsMayhemMode, Block: tx.Block, Token0AmountUint64: 0, Token1AmountUint64: 0, }, nil } func decodePumpBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data)) } var args pumpBuyArgs if err := borsh.Deserialize(&args, data[8:]); err == nil { return args.Amount, args.MaxSolCost, nil } if len(data) >= 24 { amount := binary.LittleEndian.Uint64(data[8:16]) maxSol := binary.LittleEndian.Uint64(data[16:24]) return amount, maxSol, nil } return 0, 0, fmt.Errorf("failed to parse buy tokens args") } func parsePumpBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { amount, sol, err := decodePumpBuyArgs(instruction.Data) if err != nil { return nil, err } exactIn := false if matchMethod(instruction.Data, pumpBuyV2TokensIX) { temp := amount amount = sol sol = temp exactIn = true } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } mint, err := tx.GetAccount(int(instruction.Accounts[2])) if err != nil { return nil, err } buyer, err := tx.GetAccount(int(instruction.Accounts[6])) if err != nil { return nil, err } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pump", Maker: buyer.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(amount), Token1Amount: formatSolAmount(sol), Program: "Pump", Event: "buy", ExactSOL: exactIn, IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: amount, Token1AmountUint64: sol, }, nil } func decodePumpSellArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data)) } var args pumpExtendedSellArgs if err := borsh.Deserialize(&args, data[8:]); err == nil { return args.Amount, args.MinSolOutput, nil } if len(data) >= 24 { amount := binary.LittleEndian.Uint64(data[8:16]) minSol := binary.LittleEndian.Uint64(data[16:24]) return amount, minSol, nil } return 0, 0, fmt.Errorf("failed to parse sell tokens args") } func parsePumpSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) { amount, minSol, err := decodePumpSellArgs(instruction.Data) if err != nil { return nil, err } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } mint, err := tx.GetAccount(int(instruction.Accounts[2])) if err != nil { return nil, err } seller, err := tx.GetAccount(int(instruction.Accounts[6])) if err != nil { return nil, err } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pump", Maker: seller.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(amount), Token1Amount: formatSolAmount(minSol), Program: "Pump", Event: "sell", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: amount, Token1AmountUint64: minSol, }, nil }