diff --git a/pkg/shreder/tx.go b/pkg/shreder/tx.go index cd534ed..8b67637 100644 --- a/pkg/shreder/tx.go +++ b/pkg/shreder/tx.go @@ -45,7 +45,6 @@ type TxSignal struct { IsToken2022 bool `json:"is_token2022"` IsMayhemMode bool `json:"is_mayhem_mode"` TxFee decimal.Decimal `json:"tx_fee"` - EntryContract string `json:"entry_contract"` ExactSOL bool `json:"exact_in"` diff --git a/pkg/shreder/txparser.go b/pkg/shreder/txparser.go index d9734c2..3dab9de 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -22,43 +22,31 @@ const ( // program ids var ( - pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") - pumpProgramIDString = pumpProgramID.String() + pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") // has no sell function with pump and pump.amm program - azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB") - azczProgramIDString = azczProgramID.String() + azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB") // only buy function with pump program - f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq") - f5tfProgramIDString = f5tfProgramID.String() + f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq") // only pump.fun function - photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW") - photonProgramIDString = photonProgramID.String() + photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW") - pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA") - pumpAmmProgramIDString = pumpAmmProgramID.String() + pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA") - boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM") - boboProgramIDString = boboProgramID.String() + boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM") - qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7") - qtkvProgramIDString = qtkvProgramID.String() + qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7") // only buy function with pump program - fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK") - fjszProgramIDString = fjszProgramID.String() + fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK") - flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") - flasProgramIDString = flasProgramID.String() + flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") - terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3") - terminalProgramIDString = terminalProgramID.String() + terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3") - jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") - jupiterV6ProgramIDString = jupiterV6ProgramID.String() + jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") - gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb") - gmgnProgramIDString = gmgnProgramID.String() + gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb") ) type AccountNotFoundError struct { @@ -108,6 +96,8 @@ var ( terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca} terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b} terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1} + + gmgnBuyTokensIX = []byte{0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea} ) type compiledInstruction struct { @@ -312,50 +302,53 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, switch programID { case pumpProgramID: txRes, err := parsePumpInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "pump", pumpProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "pump") case azczProgramID: txRes, err := parseAzczInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz", azczProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz") case f5tfProgramID: txRes, err := parseF5tfInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf", f5tfProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf") case flasProgramID: txRes, err := parseFlasInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "flas", flasProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "flas") case photonProgramID: txRes, err := parsePhotonInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "photon", photonProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "photon") case pumpAmmProgramID: txRes, err := parsePumpAmmInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm", pumpAmmProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm") case boboProgramID: txRes, err := parseBoboInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo", boboProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo") case qtkvProgramID: txRes, err := parseQtkvInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv", qtkvProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv") case fjszProgramID: txRes, err := parseFjszInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz", fjszProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz") case terminalProgramID: txRes, err := parseTermInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal", terminalProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal") case jupiterV6ProgramID: txRes, err := parseJupiterV6Instruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6") case okxDexRouteV2ProgramID: txRes, err := parseOkxDexRouteV2Instruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2", okxDexRouteV2ProgramIDString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2") case dflowProgramID: txRes, err := parseDFlowInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow", dflowProgramString) + parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow") + case gmgnProgramID: + txRes, err := parseGMGNInstruction(versioned, i) + parsed = appendParsed(now, parsed, txRes, err, txHash, "gmgn") } } return parsed } -func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal { +func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal { if err != nil { if !strings.HasPrefix(err.Error(), "account index") { logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) @@ -363,7 +356,6 @@ func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error return list } if parsed != nil { - parsed.EntryContract = entryContract parsed.Label = label if !start.IsZero() { parsed.ParseEnd = time.Now() @@ -1007,6 +999,66 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er }, nil } +func parseGMGNInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { + msg := tx.Message + if instructionIndex >= len(msg.Instructions) { + return nil, fmt.Errorf("instruction index out of bounds") + } + + instruction := msg.Instructions[instructionIndex] + if len(instruction.Data) == 0 { + return nil, fmt.Errorf("data is empty") + } + if len(instruction.Data) < 8 { + return nil, nil + } + + if matchMethod(instruction.Data, gmgnBuyTokensIX) { + return parseGMGNBuy(tx, &instruction) + } + return nil, nil +} + +func parseGMGNBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { + if len(instruction.Accounts) < 8 { + return nil, fmt.Errorf("accounts too short") + } + if len(instruction.Data) < 24 { + return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(instruction.Data)) + } + + staticKeys := tx.Message.StaticAccountKeys + mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + if err != nil { + return nil, err + } + user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + if err != nil { + return nil, err + } + + solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) + tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) + + return &TxSignal{ + TxHash: tx.Signatures[0].String(), + Label: "gmgn", + Maker: user.String(), + Token0Address: mint.String(), + Token1Address: wsolMint, + Token0Amount: formatTokenAmount(tokenAmount), + Token1Amount: formatSolAmount(solAmount), + Program: "Pump", + Event: "buy", + IsToken2022: false, + IsMayhemMode: false, + ExactSOL: true, + Block: tx.Block, + Token0AmountUint64: tokenAmount, + Token1AmountUint64: solAmount, + }, nil +} + func parsePhotonInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { msg := tx.Message if instructionIndex >= len(msg.Instructions) {