package shreder import ( "bytes" "encoding/binary" "fmt" "math/big" "strings" "github.com/gagliardetto/solana-go" "github.com/mr-tron/base58" "github.com/near/borsh-go" "github.com/shopspring/decimal" ) const ( wsolMint = "So11111111111111111111111111111111111111112" tokenProgram = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ) // program ids var ( pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") // has no sell function with pump and pump.amm program azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB") // only buy function with pump program f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq") // only pump.fun function photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW") pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA") boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM") qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7") // only buy function with pump program fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK") flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3") jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") ) type AccountNotFoundError struct { Index int Len int } func NewAccountNotFoundError(i, l int) error { return &AccountNotFoundError{i, l} } func (e AccountNotFoundError) Error() string { return fmt.Sprintf("account index %d out of range, len=%d", e.Index, e.Len) } // instruction discriminators 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} azczBuyTokensIX = []byte{11} azczAmmBuyTokensIX = []byte{0xf} f5tfBuyTokensIX = []byte{0} flasBuyTokensIX = []byte{0x00, 0x1, 0x4} flasSellTokensIX = []byte{0x01, 0x1, 0x3} flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2} flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2} pumpAmmBuyTokensV2IX = []byte{198, 46, 21, 82, 180, 217, 232, 112} pumpAmmBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234} pumpAmmSellTokensIX = []byte{51, 230, 133, 164, 1, 127, 131, 173} qtkvBuyTokensIX = []byte{0x02} qtkvSellTokensIX = []byte{0x03} qtkvAmmSellTokensIX = []byte{0x05} boboBuyPumpTokensIX = []byte{0xff, 0xe7, 0x11, 0x53, 0x15, 0xc5, 0xc3, 0xdf} fjszBuyTokensIX = []byte{0xe7, 0x3f, 0x99, 0x83, 0xf3, 0xed, 0xe3, 0x3c} photonBuyPumpTokensIX = []byte{0x52, 0xe1, 0x77, 0xe7, 0x4e, 0x1d, 0x2d, 0x46} photonSwapPumpAmmIX = []byte{0x2c, 0x77, 0xaf, 0xda, 0xc7, 0x4d, 0xc4, 0xeb} 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} ) type compiledInstruction struct { ProgramIDIndex uint8 Accounts []uint8 Data []byte } type addressTableLookup struct { AccountKey solana.PublicKey WritableIndexes []uint8 ReadonlyIndexes []uint8 } type versionedMessage struct { StaticAccountKeys []solana.PublicKey Instructions []compiledInstruction AddressTableLookups []addressTableLookup } type versionedTransaction struct { Signatures []solana.Signature Message versionedMessage Block uint64 } 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 } type azczBuyArgs struct { SolAmount uint64 TokenAmount uint64 } type f5tfBuyArgs struct { SolAmount uint64 TokenAmount uint64 } type flasArgs struct { Amount1 uint64 Amount2 uint64 Placeholder [3]uint8 } type photonBuyPumpArgs struct { Timestamp uint64 SolAmount uint64 TokenAmount uint64 Fee uint64 } type photonSwapPumpAmmArgs struct { FromAmount uint64 ToAmount uint64 } type pumpAmmBuyArgs struct { Amount uint64 MaxSolCost uint64 } type boboBuyArgs struct { Placeholder1 uint64 Placeholder2 uint64 SolAmount uint64 Placeholder3 uint64 Placeholder4 uint64 Placeholder5 uint64 Placeholder6 uint64 } type qtkvBuyArgs struct { Placeholder uint64 TokenNumber uint64 SolAmount uint64 } type fjszBuyArgs struct { SolAmount uint64 TokenAmount uint64 } // ParseTransaction mirrors the Rust parse_transaction entry point. func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) []*TxSignal { versioned, err := toVersionedTransaction(update) if err != nil || versioned == nil || len(versioned.Signatures) == 0 { return nil } txHash := versioned.Signatures[0] staticKeys := versioned.Message.StaticAccountKeys instructions := versioned.Message.Instructions if loader != nil && len(versioned.Message.AddressTableLookups) > 0 { lookupTableOk := true for _, lookup := range versioned.Message.AddressTableLookups { if len(lookup.WritableIndexes) == 0 { continue } accounts := loader.GetAddressTable(lookup.AccountKey, lookup.WritableIndexes) if len(accounts) != len(lookup.WritableIndexes) { lookupTableOk = false break } staticKeys = append(staticKeys, accounts...) } if lookupTableOk { for _, lookup := range versioned.Message.AddressTableLookups { if len(lookup.ReadonlyIndexes) == 0 { continue } accounts := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes) if len(accounts) != len(lookup.ReadonlyIndexes) { break } staticKeys = append(staticKeys, accounts...) } } versioned.Message.StaticAccountKeys = staticKeys } var parsed []*TxSignal for i := range instructions { inst := instructions[i] if int(inst.ProgramIDIndex) >= len(staticKeys) { continue } programID := staticKeys[inst.ProgramIDIndex] switch programID { case pumpProgramID: txRes, err := parsePumpInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "pump", pumpProgramID.String()) case azczProgramID: txRes, err := parseAzczInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "azcz", azczProgramID.String()) case f5tfProgramID: txRes, err := parseF5tfInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "f5tf", f5tfProgramID.String()) case flasProgramID: txRes, err := parseFlasInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "flas", flasProgramID.String()) case photonProgramID: txRes, err := parsePhotonInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "photon", photonProgramID.String()) case pumpAmmProgramID: txRes, err := parsePumpAmmInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "pumpamm", pumpAmmProgramID.String()) case boboProgramID: txRes, err := parseBoboInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "bobo", boboProgramID.String()) case qtkvProgramID: txRes, err := parseQtkvInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "qtkv", qtkvProgramID.String()) case fjszProgramID: txRes, err := parseFjszInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "fjsz", fjszProgramID.String()) case terminalProgramID: txRes, err := parseTermInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "terminal", terminalProgramID.String()) case jupiterV6ProgramID: txRes, err := parseJupiterV6Instruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramID.String()) case okxDexRouteV2ProgramID: txRes, err := parseOkxDexRouteV2Instruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "okxdexroutev2", okxDexRouteV2ProgramID.String()) case dflowProgramID: txRes, err := parseDFlowInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "dflow", dflowProgramID.String()) } } return parsed } func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract 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[:])) } return list } if parsed != nil { parsed.EntryContract = entryContract list = append(list, parsed) } return list } func toVersionedTransaction(update *SubscribeUpdateTransaction) (*versionedTransaction, error) { if update == nil || update.Transaction == nil || update.Transaction.Message == nil { return nil, fmt.Errorf("transaction is nil") } protoTx := update.Transaction msg := protoTx.Message signatures := make([]solana.Signature, len(protoTx.Signatures)) for i, rawSig := range protoTx.Signatures { signatures[i] = solana.SignatureFromBytes(rawSig) } staticKeys := make([]solana.PublicKey, len(msg.AccountKeys)) for i, key := range msg.AccountKeys { staticKeys[i] = solana.PublicKeyFromBytes(key) } instructions := make([]compiledInstruction, len(msg.Instructions)) for i, instr := range msg.Instructions { accounts := append([]uint8(nil), instr.Accounts...) instructions[i] = compiledInstruction{ ProgramIDIndex: uint8(instr.ProgramIdIndex), Accounts: accounts, Data: instr.Data, } } lookups := make([]addressTableLookup, len(msg.AddressTableLookups)) for i, lookup := range msg.AddressTableLookups { writable := append([]uint8(nil), lookup.WritableIndexes...) readonly := append([]uint8(nil), lookup.ReadonlyIndexes...) lookups[i] = addressTableLookup{ AccountKey: solana.PublicKeyFromBytes(lookup.AccountKey), WritableIndexes: writable, ReadonlyIndexes: readonly, } } return &versionedTransaction{ Signatures: signatures, Message: versionedMessage{ StaticAccountKeys: staticKeys, Instructions: instructions, AddressTableLookups: lookups, }, Block: update.GetSlot(), }, nil } func formatTokenAmount(amount uint64) decimal.Decimal { val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0) return val.Div(decimal.NewFromInt(1_000_000)) } func formatSolAmount(lamports uint64) decimal.Decimal { val := decimal.NewFromBigInt(new(big.Int).SetUint64(lamports), 0) return val.Div(decimal.NewFromInt(1_000_000_000)) } func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) { if index < 0 || index >= len(static) { return solana.PublicKey{}, NewAccountNotFoundError(index, len(static)) } return static[index], nil } func parsePumpInstruction(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) < 8 { return nil, fmt.Errorf("data is empty") } if matchMethod(instruction.Data[0:8], pumpBuyV2TokensIX) || matchMethod(instruction.Data[0:8], pumpBuyTokensIX) { return parsePumpBuy(tx, &instruction) } else if matchMethod(instruction.Data[0:8], pumpExtendedSellIX) { return parsePumpSell(tx, &instruction) } else if matchMethod(instruction.Data[0:8], pumpCreateCoinIX) { return parsePumpCreate(tx, &instruction) } else if matchMethod(instruction.Data[0:8], pumpCreateCoinV2IX) { return parsePumpCreateV2(tx, &instruction) } return nil, nil } func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } creator, err := getStaticKey(staticKeys, 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 *compiledInstruction) (*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)) } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } tokenProgramKey, err := getStaticKey(staticKeys, 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 *compiledInstruction) (*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") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } buyer, err := getStaticKey(staticKeys, 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 *compiledInstruction) (*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") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } seller, err := getStaticKey(staticKeys, 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 } func parseAzczInstruction(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 matchMethod(instruction.Data, azczBuyTokensIX) { return parseAzczBuy(tx, instructionIndex) } else if matchMethod(instruction.Data, azczAmmBuyTokensIX) { return parseAzczAmmBuy(tx, instructionIndex) } return nil, nil } func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } if len(instruction.Data) < 17 { return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data)) } solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9]) return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "azcz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: decimal.Zero, Token1Amount: formatSolAmount(solAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: true, Block: tx.Block, Token0AmountUint64: 0, Token1AmountUint64: solAmount, }, nil } func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } if len(instruction.Data) < 2 { return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data)) } var args azczBuyArgs 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: "azcz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenAmount), Token1Amount: formatSolAmount(args.SolAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } func parseF5tfInstruction(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 !matchMethod(instruction.Data, f5tfBuyTokensIX) { return nil, nil } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } staticKeys := msg.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 } if len(instruction.Data) < 2 { return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data)) } var args f5tfBuyArgs 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: "f5tf", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenAmount), Token1Amount: formatSolAmount(args.SolAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } func parseFlasInstruction(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) == 10 && instruction.Data[0] == 1 { return nil, nil } if len(instruction.Data) < 20 { return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data)) } methodData := instruction.Data[17:20] //if !matchMethod(methodData, flasBuyTokensIX) { // return nil, nil //} if matchMethod(methodData, flasBuyTokensIX) { return parseFlasBuy(tx, instructionIndex) } else if matchMethod(methodData, flasSellTokensIX) { return parseFlasSell(tx, instructionIndex) } else if matchMethod(methodData, flasAmmBuyTokensIX) { return parseFlasAmmBuy(tx, instructionIndex) } else if matchMethod(methodData, flasAmmSellTokensIX) { return parseFlasAmmSell(tx, instructionIndex) } return nil, nil } func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 10 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[9])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } var args flasArgs 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: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.Amount1), Token1Amount: formatSolAmount(args.Amount2), Program: "PumpAMM", Event: "sell", IsToken2022: false, IsMayhemMode: false, ExactSOL: false, Block: tx.Block, Token0AmountUint64: args.Amount1, Token1AmountUint64: args.Amount2, }, nil } func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 10 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[9])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } var args flasArgs 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: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: decimal.Zero, Token1Amount: formatSolAmount(args.Amount1), Program: "PumpAMM", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: true, Block: tx.Block, Token0AmountUint64: 0, Token1AmountUint64: args.Amount1, }, nil } func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 9 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[8])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } var args flasArgs 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: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.Amount1), Token1Amount: formatSolAmount(args.Amount2), Program: "Pump", Event: "sell", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.Amount1, Token1AmountUint64: args.Amount2, }, nil } func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 9 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[8])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } if len(instruction.Data) > 20 { instruction.Data = instruction.Data[:20] } var args flasArgs 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: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.Amount2), Token1Amount: formatSolAmount(args.Amount1), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: true, Block: tx.Block, Token0AmountUint64: args.Amount2, Token1AmountUint64: args.Amount1, }, nil } func parsePhotonInstruction(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 } switch { case bytes.Equal(instruction.Data[:8], photonBuyPumpTokensIX): return parsePhotonBuy(tx, &instruction) case bytes.Equal(instruction.Data[:8], photonSwapPumpAmmIX): return parsePhotonSwap(tx, &instruction) default: return nil, nil } } func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } var args photonBuyPumpArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } solAmount := args.SolAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "photon", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenAmount), Token1Amount: formatSolAmount(solAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.TokenAmount, Token1AmountUint64: solAmount, }, nil } func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } if !quote.Equals(solana.WrappedSol) { return nil, nil } buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } var args photonSwapPumpAmmArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse swap pump amm tokens args: %w", err) } if args.FromAmount > args.ToAmount { // sell; ignore return nil, nil } solAmount := args.FromAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "photon", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.ToAmount), Token1Amount: formatSolAmount(solAmount), Program: "PumpAMM", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.ToAmount, Token1AmountUint64: solAmount, }, nil } func parsePumpAmmInstruction(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 matchMethod(instruction.Data, pumpAmmBuyTokensIX) || matchMethod(instruction.Data, pumpAmmBuyTokensV2IX) { return parsePumpAmmBuy(tx, &instruction) } else if matchMethod(instruction.Data, pumpAmmSellTokensIX) { return parsePumpAmmSell(tx, &instruction) } return nil, nil } func parseTermInstruction(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) < 24 { return nil, nil } switch { case bytes.Equal(instruction.Data[:8], terminalBuyTokensIX): return parseTermBuy(tx, &instruction) case bytes.Equal(instruction.Data[:8], terminalSellTokensIX): return parseTermSell(tx, &instruction) case bytes.Equal(instruction.Data[:8], terminalAmmSellTokensIX): return parseTermAmmSell(tx, &instruction) default: return nil, nil } } func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) 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: "term", 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 parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } 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: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(tokenAmount), Token1Amount: formatSolAmount(solAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil } func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } 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 } tokenAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) solAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(tokenAmount), Token1Amount: formatSolAmount(solAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil } func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data)) } var args pumpAmmBuyArgs 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 parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { amount, maxSol, err := decodePumpAmmBuyArgs(instruction.Data) if err != nil { return nil, err } exactIn := false if matchMethod(instruction.Data, pumpAmmBuyTokensV2IX) { temp := amount amount = maxSol maxSol = temp exactIn = true } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } if !quote.Equals(solana.WrappedSol) { return nil, nil } buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(amount), Token1Amount: formatSolAmount(maxSol), Program: "PumpAMM", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: exactIn, Block: tx.Block, Token0AmountUint64: amount, Token1AmountUint64: maxSol, }, nil } func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { amount, minSol, err := decodePumpAmmBuyArgs(instruction.Data) if err != nil { return nil, err } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } if !quote.Equals(solana.WrappedSol) { return nil, nil } buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(amount), Token1Amount: formatSolAmount(minSol), Program: "PumpAMM", Event: "sell", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: amount, Token1AmountUint64: minSol, }, nil } func parseBoboInstruction(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 || !bytes.Equal(instruction.Data[:8], boboBuyPumpTokensIX) { return nil, nil } if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for bobo 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 } var args boboBuyArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "bobo", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: decimal.NewFromInt(1), Token1Amount: formatSolAmount(args.SolAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: 1, Token1AmountUint64: args.SolAmount, }, nil } func parseQtkvInstruction(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 matchMethod(instruction.Data, qtkvBuyTokensIX) { return parseQtkvBuy(tx, instructionIndex) } else if matchMethod(instruction.Data, qtkvAmmSellTokensIX) { return parseQtkvAmmSell(tx, instructionIndex) } else if matchMethod(instruction.Data, qtkvSellTokensIX) { return parseQtkvSell(tx, instructionIndex) } return nil, nil } func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Message.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)) } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[10])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, 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.Message.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)) } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[10])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, 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.Message.Instructions[instructionIndex] if len(instruction.Accounts) < 8 { return nil, fmt.Errorf("accounts too short") } staticKeys := tx.Message.StaticAccountKeys mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } user, err := getStaticKey(staticKeys, 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 } func parseFjszInstruction(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 !matchMethod(instruction.Data, fjszBuyTokensIX) { return nil, nil } if len(instruction.Accounts) < 7 { return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { return nil, fmt.Errorf("data too short for fjzs 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 } var args fjszBuyArgs if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Label: "fjsz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(args.TokenAmount), Token1Amount: formatSolAmount(args.SolAmount), Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } func parseTerminalInstruction(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 matchMethod(instruction.Data, terminalBuyTokensIX) { return parseTermBuy(tx, &instruction) } else if matchMethod(instruction.Data, terminalSellTokensIX) { return parseTermSell(tx, &instruction) } else if matchMethod(instruction.Data, terminalAmmSellTokensIX) { return parseTermAmmSell(tx, &instruction) } return nil, nil } func indexOf(haystack []uint8, needle uint8) int { for i, v := range haystack { if v == needle { return i } } return -1 } func matchMethod(data []byte, methods []byte) bool { if len(data) < len(methods) { return false } return bytes.Equal(data[0:len(methods)], methods) }