package pump_parser import ( "errors" "slices" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) var defaultSwapPrograms = map[solana.PublicKey]swapParser{ pumpAmmProgram: pumpAmmParser, pumpProgram: pumpParser, } var errTxParserPrograms = map[solana.PublicKey]swapParser{ pumpAmmProgram: pumpAmmParser, pumpProgram: pumpParser, } var swapPrograms = cloneSwapPrograms(defaultSwapPrograms) type ParserOption func(*parserConfig) type parserConfig struct { enableMeteoraDlmm bool } func EnableAllParsers() { programs := cloneSwapPrograms(defaultSwapPrograms) programs[meteoraDlmmProgram] = metaoradlmmParser programs[metaoraPoolProgramID] = metaoraPoolParser programs[metaoraBcProgramID] = metaoraBcParser programs[meteoraDammV2Program] = metaoraDammParser programs[orcaProgramID] = orcaWhirPoolParser programs[raydiumV4Program] = raydiumV4Parser programs[raydiumClmmProgramID] = raydiumClmmParser programs[raydiumCPmmProgramID] = raydiumCPmmParser programs[raydiumLaunchLabProgramID] = raydiumLaunchLabParser swapPrograms = programs } func InitParser(opts ...ParserOption) { cfg := parserConfig{} for _, opt := range opts { opt(&cfg) } programs := cloneSwapPrograms(defaultSwapPrograms) if cfg.enableMeteoraDlmm { programs[meteoraDlmmProgram] = metaoradlmmParser } swapPrograms = programs } func WithMeteoraDlmm() ParserOption { return func(cfg *parserConfig) { cfg.enableMeteoraDlmm = true } } var actionPrograms = map[solana.PublicKey]actionParser{ systemProgram: systemParser, budgGetProgram: budgetParser, chainLinkProgram: chainLinkParser, } func ParseRawTx(rawTx *RawTx) (*Tx, error) { tx := &Tx{ rawTx: rawTx, } err := tx.Parser() if err != nil { return nil, err } return tx, nil } func (tx *Tx) Parser() error { if tx.rawTx == nil { return errors.New("rawTx is nil") } accountList := tx.rawTx.getAccountList() if tx.rawTx.Meta.Err == nil { for _, acc := range tx.rawTx.Transaction.Message.Instructions { if accountList[acc.ProgramIDIndex] == solana.VoteProgramID { tx.Vote = true } } } tx.TxHash = (*[64]byte)((tx.rawTx.Transaction.Signatures[0][:])) tx.Signer = tx.rawTx.GetSigner() tx.Block = tx.rawTx.Slot tx.BlockIndex = uint64(tx.rawTx.IndexWithinBlock) tx.BlockAt = tx.rawTx.BlockTime tx.CuFee = decimal.NewFromUint64(tx.rawTx.Meta.Fee) tx.ComputeUnitsConsumed = tx.rawTx.Meta.ComputeUnitsConsumed tx.BeforeSolBalance = decimal.NewFromUint64(tx.rawTx.Meta.PreBalances[0]).Div(decimal.NewFromInt(1e9)) tx.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[0]).Div(decimal.NewFromInt(1e9)) tx.Token = make(map[solana.PublicKey]TokenMeta) if tx.rawTx.Meta.Err != nil { tx.Err = tx.rawTx.Meta.Err if tx.Err.UnKnown != "" { return nil } if len(tx.rawTx.Transaction.Message.Instructions) <= int(tx.Err.Index) { return nil } programIdx := tx.rawTx.Transaction.Message.Instructions[tx.Err.Index].ProgramIDIndex if len(accountList) <= programIdx { return nil } programAccount := accountList[programIdx] parserFunc, exists := errTxParserPrograms[programAccount] if !exists { return nil } // parse failed tx swaps, _, err := parserFunc(tx, tx.rawTx.Transaction.Message.Instructions[tx.Err.Index], InnerInstructions{}, [2]uint{uint(tx.Err.Index), uint(0)}) if err != nil { return nil //fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash()) } if len(swaps) > 0 { for i := range swaps { swaps[i].InstrIdx = tx.Err.Index } tx.Swaps = swaps } for i, instr := range tx.rawTx.Transaction.Message.Instructions { if p, exists := actionPrograms[accountList[instr.ProgramIDIndex]]; exists { _, err := p(tx, instr, InnerInstructions{}, [2]uint{uint(i), uint(0)}) if err != nil { if errors.Is(err, InstructionIgnoredError) { continue } continue } } } return nil } var innersMap = make(map[int]InnerInstructions) for _, inner := range tx.rawTx.Meta.InnerInstructions { innersMap[inner.Index] = inner } txIndex := 0 for i, instr := range tx.rawTx.Transaction.Message.Instructions { txIndex += 1 if i > 0 { txIndex += len(innersMap[i-1].Instructions) } programAccount := accountList[instr.ProgramIDIndex] if p, exists := swapPrograms[programAccount]; exists { swaps, _, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)}) if err != nil { if errors.Is(err, InstructionIgnoredError) { continue } return err } for k, swap := range swaps { swap.TxIndex = txIndex + k if !swap.User.IsOnCurve() { swap.AfterSOLBalance = tx.AfterSOLBalance swap.User = tx.rawTx.accountList[0] } else { userIdx := slices.Index(tx.rawTx.accountList, swap.User) if userIdx >= 0 { swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) } } swap.InstrIdx = uint8(i) tx.Swaps = append(tx.Swaps, swap) } } else if p, exists := actionPrograms[programAccount]; exists { _, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)}) if err != nil { if errors.Is(err, InstructionIgnoredError) { continue } return err } } else { ii := i // unknown program, parser inner instructions innerLength := len(innersMap[i].Instructions) for j := 1; j <= innerLength; { if j <= 0 || j > innerLength { //log.Printf("inner instruction index is out if range, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.Block, tx.GetTxHash(), ii, j) break } innerInstr := innersMap[i].Instructions[j-1] innerProgramAccount := accountList[innerInstr.ProgramIDIndex] if p, exists := swapPrograms[innerProgramAccount]; exists { swaps, offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)}) if err != nil { if errors.Is(err, InstructionIgnoredError) { j = int(offset[1]) continue } return err } for k, swap := range swaps { swap.TxIndex = txIndex + k + j // identify okxDexRoutersV2 and okxAggregatorV2 is user //if !swap.User.IsOnCurve() && (swap.EntryContract.Equals(okxDexRoutersV2) || swap.EntryContract.Equals(okxAggregatorV2)) { // swap.User = tx.rawTx.accountList[0] //} if !swap.User.IsOnCurve() { swap.AfterSOLBalance = tx.AfterSOLBalance swap.User = tx.rawTx.accountList[0] } else { userIdx := slices.Index(tx.rawTx.accountList, swap.User) if userIdx >= 0 { swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) } } swap.InstrIdx = uint8(i) tx.Swaps = append(tx.Swaps, swap) } // tx.Swaps = append(tx.Swaps, swaps...) if ii == int(offset[0]) && j == int(offset[1]) { j = j + 1 } else { j = int(offset[1]) ii = int(offset[0]) } } else if p, exists := actionPrograms[innerProgramAccount]; exists { offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)}) if err != nil { if errors.Is(err, InstructionIgnoredError) { j = int(offset[1]) continue } return err } j = int(offset[1]) ii = int(offset[0]) } else { j++ } if j > innerLength || ii > i { break } } } } // update swaps same program+pair with last reserve balance if len(tx.Swaps) > 1 { pairKey := func(s Swap) solana.PublicKey { // Match pair selection used by downstream consumers. if s.Program == SolProgramPump { return s.BaseMint } return s.Pool } lastReserve := make(map[solana.PublicKey]reserveSnapshot, len(tx.Swaps)) for _, swap := range tx.Swaps { lastReserve[pairKey(swap)] = reserveSnapshot{ baseMint: swap.BaseMint, quoteMint: swap.QuoteMint, baseReserve: swap.BaseReserve, quoteReserve: swap.QuoteReserve, } } for i := range tx.Swaps { key := pairKey(tx.Swaps[i]) if v, ok := lastReserve[key]; ok { if tx.Swaps[i].BaseMint == v.baseMint && tx.Swaps[i].QuoteMint == v.quoteMint { tx.Swaps[i].BaseReserve = v.baseReserve tx.Swaps[i].QuoteReserve = v.quoteReserve } else if tx.Swaps[i].BaseMint == v.quoteMint && tx.Swaps[i].QuoteMint == v.baseMint { tx.Swaps[i].BaseReserve = v.quoteReserve tx.Swaps[i].QuoteReserve = v.baseReserve } //else { // tx.Swaps[i].BaseReserve = v.baseReserve // tx.Swaps[i].QuoteReserve = v.quoteReserve //} } } } return nil } type reserveSnapshot struct { baseMint solana.PublicKey quoteMint solana.PublicKey baseReserve decimal.Decimal quoteReserve decimal.Decimal } func cloneSwapPrograms(src map[solana.PublicKey]swapParser) map[solana.PublicKey]swapParser { dst := make(map[solana.PublicKey]swapParser, len(src)) for k, v := range src { dst[k] = v } return dst }