package pump_parser import ( "fmt" "github.com/shopspring/decimal" ) func raydiumV4Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumV4Program) { return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 instruction not found, offset, %d, %d", offset[0], offset[1]) } decode := instruction.Data if len(decode) < 1 { return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 program instruction data too short, offset, %d, %d", offset[0], offset[1]) } discriminator := decode[0] switch discriminator { case raydiumV4InitializePoolDiscriminator: return raydiumv4InitializeParser(tx, instruction, innerInstructions, offset) case raydiumV4AddLiquidityDiscriminator: return raydiumv4AddLiquidityParser(tx, instruction, innerInstructions, offset) case raydiumV4RemoveLiquidityDiscriminator: return raydiumv4RemoveLiquidityParser(tx, instruction, innerInstructions, offset) case raydiumV4WithdrawPNLDiscriminator: return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset) case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator: return raydiumv4SwapParser(tx, instruction, innerInstructions, offset) case raydiumV4SwapBaseInV2Discriminator, raydiumV4SwapBaseOutV2Discriminator: return raydiumv4SwapV2Parser(tx, instruction, innerInstructions, offset) default: return nil, increaseOffset(offset), InstructionIgnoredError } } func raydiumv4InitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) if accountsLen != 21 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 initialize instruction, offset %d, %d", offset[0], offset[1]) } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] //who := tx.rawTx.accountList[instruction.Accounts[accountsLen-1]] user := tx.rawTx.accountList[instruction.Accounts[accountsLen-4]] baseVaultIdx := instruction.Accounts[10] quoteVaultIdx := instruction.Accounts[11] baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) offset[1] += 30 return []Swap{ { Program: SolProgramRaydiumV4, Event: "create", Pool: tx.rawTx.accountList[instruction.Accounts[4]], BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, Creator: user, BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), User: user, BaseReserve: baseReserve, QuoteReserve: quoteReserve, EntryContract: entryContract, }, }, offset, nil } func raydiumv4AddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) if accountsLen != 14 && accountsLen != 15 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 add liquidity instruction, offset %d, %d", offset[0], offset[1]) } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] baseVaultAccountIndex := instruction.Accounts[6] quoteVaultAccountIndex := instruction.Accounts[7] userBaseVaultAccountIndex := instruction.Accounts[9] userQuoteVaultAccountIndex := instruction.Accounts[10] baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), err } var nextIndex int var baseFound, quoteFound bool var baseAmount, quoteAmount decimal.Decimal for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue } if from.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && to.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound { baseAmount = decimal.NewFromUint64(amount) baseFound = true } else if from.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && to.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound { quoteAmount = decimal.NewFromUint64(amount) quoteFound = true } if baseFound && quoteFound { nextIndex = i + 1 break } } if !baseFound || !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for add liquidity, offset %d, %d", offset[0], offset[1]) } offset[1] += uint(nextIndex + 1) baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) return []Swap{ { Program: SolProgramRaydiumV4, Event: "remove_liquidity", Pool: tx.rawTx.accountList[instruction.Accounts[1]], BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, User: tx.rawTx.accountList[instruction.Accounts[12]], BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, BaseAmount: baseAmount, QuoteAmount: quoteAmount, EntryContract: entryContract, }, }, offset, nil } func raydiumv4RemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) const AccountLen = 20 if accountsLen != AccountLen && accountsLen != AccountLen+1 && accountsLen != AccountLen+2 && accountsLen != AccountLen+3 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 add liquidity instruction, offset %d, %d", offset[0], offset[1]) } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] baseVaultAccountIndex := instruction.Accounts[6] quoteVaultAccountIndex := instruction.Accounts[7] userBaseVaultAccountIndex := instruction.Accounts[14] userQuoteVaultAccountIndex := instruction.Accounts[16] if accountsLen == AccountLen+2 || accountsLen == AccountLen+3 { userBaseVaultAccountIndex = instruction.Accounts[16] userQuoteVaultAccountIndex = instruction.Accounts[17] } baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), err } var nextIndex int var baseFound, quoteFound bool var baseAmount, quoteAmount decimal.Decimal for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue } if to.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound { baseAmount = decimal.NewFromUint64(amount) baseFound = true } else if to.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound { quoteAmount = decimal.NewFromUint64(amount) quoteFound = true } if baseFound && quoteFound { nextIndex = i + 1 break } } if !baseFound || !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for add liquidity, offset %d, %d", offset[0], offset[1]) } offset[1] += uint(nextIndex + 1) baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) return []Swap{ { Program: SolProgramRaydiumV4, Event: "remove_liquidity", Pool: tx.rawTx.accountList[instruction.Accounts[1]], BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, User: tx.rawTx.accountList[0], BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, BaseAmount: baseAmount, QuoteAmount: quoteAmount, EntryContract: entryContract, }, }, offset, nil } func raydiumv4WithdrawPNLParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) if accountsLen != 17 && accountsLen != 18 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 WithdrawPNL instruction, offset %d, %d", offset[0], offset[1]) } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] baseVaultAccountIndex := instruction.Accounts[5] quoteVaultAccountIndex := instruction.Accounts[6] userBaseVaultAccountIndex := instruction.Accounts[7] userQuoteVaultAccountIndex := instruction.Accounts[8] baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), err } var nextIndex int var baseFound, quoteFound bool var baseAmount, quoteAmount decimal.Decimal for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue } if to.Equals(tx.rawTx.accountList[userBaseVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[baseVaultAccountIndex]) && !baseFound { baseAmount = decimal.NewFromUint64(amount) baseFound = true } else if to.Equals(tx.rawTx.accountList[userQuoteVaultAccountIndex]) && from.Equals(tx.rawTx.accountList[quoteVaultAccountIndex]) && !quoteFound { quoteAmount = decimal.NewFromUint64(amount) quoteFound = true } if baseFound && quoteFound { nextIndex = i + 1 break } } if !baseFound || !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer inner instruction for with pnl, offset %d, %d", offset[0], offset[1]) } offset[1] += uint(nextIndex + 1) baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) return []Swap{ { Program: SolProgramRaydiumV4, Event: "remove_liquidity", Pool: tx.rawTx.accountList[instruction.Accounts[1]], BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, User: tx.rawTx.accountList[instruction.Accounts[9]], BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, BaseAmount: baseAmount, QuoteAmount: quoteAmount, EntryContract: entryContract, }, }, offset, nil } func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) if accountsLen != 17 && accountsLen != 18 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swap instruction, offset %d, %d", offset[0], offset[1]) } user := tx.rawTx.accountList[instruction.Accounts[accountsLen-1]] userSrcIdx := instruction.Accounts[accountsLen-3] userDestIdx := instruction.Accounts[accountsLen-2] vaultBaseIdx := instruction.Accounts[4] vaultQuoteIdx := instruction.Accounts[5] if accountsLen == 18 { vaultBaseIdx = instruction.Accounts[5] vaultQuoteIdx = instruction.Accounts[6] } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] ammAccount := tx.rawTx.accountList[instruction.Accounts[1]] userSourceTokenAccount := tx.rawTx.accountList[userSrcIdx] userDestinationTokenAccount := tx.rawTx.accountList[userDestIdx] baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, vaultBaseIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, vaultQuoteIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), err } var nextIndex int var srcFound, destFound bool var baseAmount, quoteAmount decimal.Decimal var event string for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue } if from.Equals(userSourceTokenAccount) && !srcFound { if to.Equals(tx.rawTx.accountList[vaultBaseIdx]) { event = "sell" baseAmount = decimal.NewFromUint64(amount) srcFound = true } else if to.Equals(tx.rawTx.accountList[vaultQuoteIdx]) { event = "buy" quoteAmount = decimal.NewFromUint64(amount) srcFound = true } } else if to.Equals(userDestinationTokenAccount) && !destFound { if from.Equals(tx.rawTx.accountList[vaultQuoteIdx]) { event = "sell" quoteAmount = decimal.NewFromUint64(amount) destFound = true } else if from.Equals(tx.rawTx.accountList[vaultBaseIdx]) { event = "buy" baseAmount = decimal.NewFromUint64(amount) destFound = true } } if srcFound && destFound { nextIndex = i + 1 break } } if !srcFound || !destFound { return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 failed to find token transfer inner instruction for swap, offset %d, %d", offset[0], offset[1]) } offset[1] += uint(nextIndex + 1) userBase := getAccountBalanceAfterTx(tx.rawTx, userSrcIdx) userQuote := getAccountBalanceAfterTx(tx.rawTx, userDestIdx) baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) return []Swap{ { Program: SolProgramRaydiumV4, Event: event, Pool: ammAccount, BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), User: user, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, Mayhem: false, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil } func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accountsLen := len(instruction.Accounts) if accountsLen != 8 { return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1]) } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] ammAccount := tx.rawTx.accountList[instruction.Accounts[1]] user := tx.rawTx.accountList[instruction.Accounts[7]] userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]] userDestinationTokenAccount := tx.rawTx.accountList[instruction.Accounts[6]] baseVaultIdx := instruction.Accounts[3] quoteVaultIdx := instruction.Accounts[4] baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err) } quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err) } inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), err } var nextIndex int var srcFound, destFound bool var baseAmount, quoteAmount decimal.Decimal var event string for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue } if from.Equals(userSourceTokenAccount) && !srcFound { if to.Equals(tx.rawTx.accountList[baseVaultIdx]) { event = "sell" baseAmount = decimal.NewFromUint64(amount) srcFound = true } else if to.Equals(tx.rawTx.accountList[quoteVaultIdx]) { event = "buy" quoteAmount = decimal.NewFromUint64(amount) srcFound = true } } else if to.Equals(userDestinationTokenAccount) && !destFound { if from.Equals(tx.rawTx.accountList[quoteVaultIdx]) { event = "sell" quoteAmount = decimal.NewFromUint64(amount) destFound = true } else if from.Equals(tx.rawTx.accountList[baseVaultIdx]) { event = "buy" baseAmount = decimal.NewFromUint64(amount) destFound = true } } if srcFound && destFound { nextIndex = i + 1 break } } if !srcFound || !destFound { return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 failed to find token transfer inner instruction for swapv2, offset %d, %d", offset[0], offset[1]) } offset[1] += uint(nextIndex + 1) userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[5]) userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[6]) baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount) return []Swap{ { Program: SolProgramRaydiumV4, Event: event, Pool: ammAccount, BaseMint: baseTokenbalance.MintAccount, QuoteMint: quoteTokenbalance.MintAccount, BaseTokenProgram: baseTokenbalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals), User: user, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, Mayhem: false, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, }, }, offset, nil }