Files
pump-parser/parser.go

307 lines
8.6 KiB
Go
Raw Normal View History

2025-11-20 17:56:45 +08:00
package pump_parser
import (
"errors"
2026-02-09 14:46:19 +08:00
"slices"
2025-11-20 17:56:45 +08:00
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
2026-01-08 12:00:59 +08:00
var defaultSwapPrograms = map[solana.PublicKey]swapParser{
pumpAmmProgram: pumpAmmParser,
pumpProgram: pumpParser,
}
2026-02-26 16:11:34 +08:00
var errTxParserPrograms = map[solana.PublicKey]swapParser{
pumpAmmProgram: pumpAmmParser,
pumpProgram: pumpParser,
}
2026-01-08 12:00:59 +08:00
var swapPrograms = cloneSwapPrograms(defaultSwapPrograms)
type ParserOption func(*parserConfig)
type parserConfig struct {
enableMeteoraDlmm bool
}
2026-02-09 14:46:19 +08:00
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
}
2026-01-08 12:00:59 +08:00
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
}
2025-11-20 17:56:45 +08:00
}
var actionPrograms = map[solana.PublicKey]actionParser{
2026-03-02 18:01:32 +08:00
systemProgram: systemParser,
budgGetProgram: budgetParser,
chainLinkProgram: chainLinkParser,
2025-11-20 17:56:45 +08:00
}
2025-11-27 16:39:01 +08:00
func ParseRawTx(rawTx *RawTx) (*Tx, error) {
tx := &Tx{
rawTx: rawTx,
}
err := tx.Parser()
if err != nil {
return nil, err
}
return tx, nil
}
2025-11-21 12:01:44 +08:00
func (tx *Tx) Parser() error {
if tx.rawTx == nil {
return errors.New("rawTx is nil")
2025-11-20 17:56:45 +08:00
}
2025-11-21 12:01:44 +08:00
accountList := tx.rawTx.getAccountList()
2026-02-02 14:13:00 +08:00
if tx.rawTx.Meta.Err == nil {
for _, acc := range tx.rawTx.Transaction.Message.Instructions {
if accountList[acc.ProgramIDIndex] == solana.VoteProgramID {
tx.Vote = true
}
}
}
2025-11-21 12:01:44 +08:00
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
2026-01-05 11:46:54 +08:00
tx.CuFee = decimal.NewFromUint64(tx.rawTx.Meta.Fee)
2025-11-21 12:01:44 +08:00
2026-02-11 17:49:43 +08:00
tx.ComputeUnitsConsumed = tx.rawTx.Meta.ComputeUnitsConsumed
2025-11-21 12:01:44 +08:00
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)
2026-02-26 16:11:34 +08:00
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 {
tx.Swaps = swaps
}
for i, instr := range tx.rawTx.Transaction.Message.Instructions {
2026-02-27 17:07:49 +08:00
if p, exists := actionPrograms[accountList[instr.ProgramIDIndex]]; exists {
2026-02-26 16:11:34 +08:00
_, err := p(tx, instr, InnerInstructions{}, [2]uint{uint(i), uint(0)})
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
continue
}
continue
}
}
}
return nil
}
2025-11-20 17:56:45 +08:00
var innersMap = make(map[int]InnerInstructions)
2025-11-21 12:01:44 +08:00
for _, inner := range tx.rawTx.Meta.InnerInstructions {
2025-11-20 17:56:45 +08:00
innersMap[inner.Index] = inner
}
2026-02-09 14:46:19 +08:00
txIndex := 0
2025-11-21 12:01:44 +08:00
for i, instr := range tx.rawTx.Transaction.Message.Instructions {
2026-02-09 14:46:19 +08:00
txIndex += 1
if i > 0 {
txIndex += len(innersMap[i-1].Instructions)
}
2025-11-20 17:56:45 +08:00
programAccount := accountList[instr.ProgramIDIndex]
if p, exists := swapPrograms[programAccount]; exists {
2025-11-21 12:01:44 +08:00
swaps, _, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
2025-11-20 17:56:45 +08:00
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
continue
}
2025-11-21 12:01:44 +08:00
return err
2025-11-20 17:56:45 +08:00
}
2026-02-09 14:46:19 +08:00
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))
}
}
tx.Swaps = append(tx.Swaps, swap)
}
2025-11-20 17:56:45 +08:00
} else if p, exists := actionPrograms[programAccount]; exists {
2025-11-21 12:01:44 +08:00
_, err := p(tx, instr, innersMap[i], [2]uint{uint(i), uint(0)})
2025-11-20 17:56:45 +08:00
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
continue
}
2025-11-21 12:01:44 +08:00
return err
2025-11-20 17:56:45 +08:00
}
} else {
ii := i
// unknown program, parser inner instructions
innerLength := len(innersMap[i].Instructions)
for j := 1; j <= innerLength; {
2026-01-02 10:42:44 +08:00
if j <= 0 || j > innerLength {
2026-02-26 16:11:34 +08:00
//log.Printf("inner instruction index is out if range, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.Block, tx.GetTxHash(), ii, j)
2026-01-02 10:42:44 +08:00
break
}
2025-11-20 17:56:45 +08:00
innerInstr := innersMap[i].Instructions[j-1]
innerProgramAccount := accountList[innerInstr.ProgramIDIndex]
if p, exists := swapPrograms[innerProgramAccount]; exists {
2025-11-21 12:01:44 +08:00
swaps, offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)})
2025-11-20 17:56:45 +08:00
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
j = int(offset[1])
continue
}
2025-11-21 12:01:44 +08:00
return err
2025-11-20 17:56:45 +08:00
}
2026-02-09 14:46:19 +08:00
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))
}
}
tx.Swaps = append(tx.Swaps, swap)
}
// tx.Swaps = append(tx.Swaps, swaps...)
2026-02-21 09:29:28 +08:00
if ii == int(offset[0]) && j == int(offset[1]) {
j = j + 1
} else {
j = int(offset[1])
ii = int(offset[0])
}
2025-11-20 17:56:45 +08:00
} else if p, exists := actionPrograms[innerProgramAccount]; exists {
2025-11-21 12:01:44 +08:00
offset, err := p(tx, innerInstr, innersMap[i], [2]uint{uint(i), uint(j)})
2025-11-20 17:56:45 +08:00
if err != nil {
if errors.Is(err, InstructionIgnoredError) {
j = int(offset[1])
continue
}
2025-11-21 12:01:44 +08:00
return err
2025-11-20 17:56:45 +08:00
}
j = int(offset[1])
ii = int(offset[0])
} else {
j++
}
if j > innerLength || ii > i {
break
}
}
}
}
2026-02-09 14:46:19 +08:00
// 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
//}
}
}
}
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
return nil
2025-11-20 17:56:45 +08:00
}
2026-01-08 12:00:59 +08:00
2026-02-09 14:46:19 +08:00
type reserveSnapshot struct {
baseMint solana.PublicKey
quoteMint solana.PublicKey
baseReserve decimal.Decimal
quoteReserve decimal.Decimal
}
2026-01-08 12:00:59 +08:00
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
}