package shreder import ( "encoding/binary" "fmt" "github.com/gagliardetto/solana-go" "github.com/near/borsh-go" "github.com/shopspring/decimal" ) var qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7") var ( qtkvBuyTokensIX = []byte{0x02} qtkvSellTokensIX = []byte{0x03} qtkvAmmSellTokensIX = []byte{0x05} ) type qtkvBuyArgs struct { Placeholder uint64 TokenNumber uint64 SolAmount uint64 } func parseQtkvInstruction(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) == 0 { return nil, fmt.Errorf("data is empty") } var ( err error txSignal *TxSignal ) if matchMethod(instruction.Data, qtkvBuyTokensIX) { txSignal, err = parseQtkvBuy(tx, instructionIndex) } else if matchMethod(instruction.Data, qtkvAmmSellTokensIX) { txSignal, err = parseQtkvAmmSell(tx, instructionIndex) } else if matchMethod(instruction.Data, qtkvSellTokensIX) { txSignal, err = parseQtkvSell(tx, instructionIndex) } if txSignal != nil { return TxSignalBatch{txSignal}, err } return nil, err } func parseQtkvSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Instructions[instructionIndex] if len(instruction.Accounts) < 11 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data)) } mint, err := tx.GetAccount(int(instruction.Accounts[10])) if err != nil { return nil, err } user, err := tx.GetAccount(int(instruction.Accounts[0])) if err != nil { return nil, err } // in sell, sol amount is not directly provided, so we set it to 0 tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25]) return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(tokenAmount), Token1Amount: decimal.Zero, Program: "Pump", Event: "sell", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: tokenAmount, Token1AmountUint64: 0, }, nil } func parseQtkvAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Instructions[instructionIndex] if len(instruction.Accounts) < 11 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data)) } mint, err := tx.GetAccount(int(instruction.Accounts[10])) if err != nil { return nil, err } user, err := tx.GetAccount(int(instruction.Accounts[0])) if err != nil { return nil, err } // in sell, sol amount is not directly provided, so we set it to 0 tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25]) return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(tokenAmount), Token1Amount: decimal.Zero, Program: "PumpAMM", Event: "sell", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: tokenAmount, Token1AmountUint64: 0, }, nil } func parseQtkvBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Instructions[instructionIndex] if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } mint, err := tx.GetAccount(int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := tx.GetAccount(int(instruction.Accounts[0])) if err != nil { return nil, err } var args qtkvBuyArgs if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenNumber), Token1Amount: formatSolAmount(args.SolAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.TokenNumber, Token1AmountUint64: args.SolAmount, }, nil }