409 lines
16 KiB
Go
409 lines
16 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
func raydiumCPmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumCPmmProgramID) {
|
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumCPmm instruction not found, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumCPmm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
|
|
switch discriminator {
|
|
case raydiumCPmmInitializeDiscriminator, raydiumCPmmInitializeWithPermissionDiscriminator:
|
|
return raydiumCPmmCreatePoolParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumCPmmDepositDiscriminator:
|
|
return raydiumCPmmDepositParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumCPmmWithdrawDiscriminator:
|
|
return raydiumCPmmWithdrawParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumCPmmCollectProtocolFeeDiscriminator, raydiumCPmmCollectFundFeeDiscriminator:
|
|
return raydiumCPmmCollectParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumCPmmSwapBaseInputDiscriminator, raydiumCPmmSwapBaseOutputDiscriminator:
|
|
return raydiumCPmmSwapParser(tx, instruction, innerInstructions, offset)
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
}
|
|
|
|
func raydiumCPmmCreatePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 20 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize instruction")
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
|
|
pool := tx.rawTx.accountList[instruction.Accounts[3]]
|
|
lpMint := tx.rawTx.accountList[instruction.Accounts[6]]
|
|
creator := tx.rawTx.accountList[instruction.Accounts[0]]
|
|
vault0 := instruction.Accounts[10]
|
|
vault1 := instruction.Accounts[11]
|
|
if bytes.Equal(instruction.Data[:8], raydiumCPmmInitializeWithPermissionDiscriminator[:]) {
|
|
pool = tx.rawTx.accountList[instruction.Accounts[4]]
|
|
lpMint = tx.rawTx.accountList[instruction.Accounts[7]]
|
|
creator = tx.rawTx.accountList[instruction.Accounts[1]]
|
|
vault0 = instruction.Accounts[11]
|
|
vault1 = instruction.Accounts[12]
|
|
}
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[vault0])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
|
}
|
|
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[vault1])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
|
}
|
|
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
offset[1] += 13
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCPMM,
|
|
Event: "create",
|
|
Pool: pool,
|
|
BaseMint: baseTokenBalance.MintAccount,
|
|
QuoteMint: quoteTokenBalance.MintAccount,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
Creator: creator,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
LpMint: lpMint,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, increaseOffset(offset), nil
|
|
}
|
|
|
|
func raydiumCPmmDepositParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 13 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
|
token0User := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
token1User := tx.rawTx.accountList[instruction.Accounts[5]]
|
|
|
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[6]]
|
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[7]]
|
|
|
|
token0 := tx.rawTx.accountList[instruction.Accounts[10]]
|
|
token1 := tx.rawTx.accountList[instruction.Accounts[11]]
|
|
lpTokenMint := tx.rawTx.accountList[instruction.Accounts[12]]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
|
}
|
|
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
|
}
|
|
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
|
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
|
|
|
var baseFound, quoteFound bool
|
|
var baseAmount, quoteAmount decimal.Decimal
|
|
for _, inner := range inners {
|
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if from.Equals(token0User) && to.Equals(token0Vault) && !baseFound {
|
|
baseFound = true
|
|
baseAmount = decimal.NewFromUint64(amount)
|
|
} else if from.Equals(token1User) && to.Equals(token1Vault) && !quoteFound {
|
|
quoteFound = true
|
|
quoteAmount = decimal.NewFromUint64(amount)
|
|
}
|
|
if baseFound && quoteFound {
|
|
break
|
|
}
|
|
}
|
|
if !baseFound || !quoteFound {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
|
}
|
|
offset[1] += 3
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCPMM,
|
|
Event: "add_liquidity",
|
|
Pool: market,
|
|
BaseMint: token0,
|
|
QuoteMint: token1,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
LpMint: lpTokenMint,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumCPmmWithdrawParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 13 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
|
token0User := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
token1User := tx.rawTx.accountList[instruction.Accounts[5]]
|
|
|
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[6]]
|
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[7]]
|
|
|
|
token0 := tx.rawTx.accountList[instruction.Accounts[10]]
|
|
token1 := tx.rawTx.accountList[instruction.Accounts[11]]
|
|
lpTokenMint := tx.rawTx.accountList[instruction.Accounts[12]]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
|
}
|
|
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
|
}
|
|
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
|
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
|
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %v", err)
|
|
}
|
|
var baseFound, quoteFound bool
|
|
var baseAmount, quoteAmount decimal.Decimal
|
|
for _, inner := range inners {
|
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if to.Equals(token0User) && from.Equals(token0Vault) && !baseFound {
|
|
baseFound = true
|
|
baseAmount = decimal.NewFromUint64(amount)
|
|
} else if to.Equals(token1User) && from.Equals(token1Vault) && !quoteFound {
|
|
quoteFound = true
|
|
quoteAmount = decimal.NewFromUint64(amount)
|
|
}
|
|
if baseFound && quoteFound {
|
|
break
|
|
}
|
|
}
|
|
if !baseFound || !quoteFound {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
|
}
|
|
offset[1] += 3
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCPMM,
|
|
Event: "remove_liquidity",
|
|
Pool: market,
|
|
BaseMint: token0,
|
|
QuoteMint: token1,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
LpMint: lpTokenMint,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumCPmmCollectParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 12 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for deposit instruction")
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
market := tx.rawTx.accountList[instruction.Accounts[2]]
|
|
token0User := tx.rawTx.accountList[instruction.Accounts[8]]
|
|
token1User := tx.rawTx.accountList[instruction.Accounts[9]]
|
|
|
|
token0Vault := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
token1Vault := tx.rawTx.accountList[instruction.Accounts[5]]
|
|
|
|
token0 := tx.rawTx.accountList[instruction.Accounts[6]]
|
|
token1 := tx.rawTx.accountList[instruction.Accounts[7]]
|
|
|
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
|
|
|
var baseFound, quoteFound bool
|
|
var baseAmount, quoteAmount decimal.Decimal
|
|
for _, inner := range inners {
|
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if to.Equals(token0User) && from.Equals(token0Vault) && !baseFound {
|
|
baseFound = true
|
|
baseAmount = decimal.NewFromUint64(amount)
|
|
} else if to.Equals(token1User) && from.Equals(token1Vault) && !quoteFound {
|
|
quoteFound = true
|
|
quoteAmount = decimal.NewFromUint64(amount)
|
|
}
|
|
if baseFound && quoteFound {
|
|
break
|
|
}
|
|
}
|
|
if !baseFound && !quoteFound {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[4])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err)
|
|
}
|
|
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err)
|
|
}
|
|
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
|
|
event := "remove_liquidity"
|
|
if !baseFound || !quoteFound {
|
|
offset[1] += 1
|
|
event = "remove_liquidity_one_side"
|
|
} else {
|
|
offset[1] += 2
|
|
}
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCPMM,
|
|
Event: event,
|
|
Pool: market,
|
|
BaseMint: token0,
|
|
QuoteMint: token1,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumCPmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 13 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for SwapBaseInputParser instruction")
|
|
}
|
|
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
market := tx.rawTx.accountList[instruction.Accounts[3]]
|
|
// Get token accounts from instruction
|
|
tokenIn := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
tokenOut := tx.rawTx.accountList[instruction.Accounts[5]]
|
|
user := tx.rawTx.accountList[instruction.Accounts[0]]
|
|
|
|
user0 := instruction.Accounts[4]
|
|
user1 := instruction.Accounts[5]
|
|
|
|
inputVault := tx.rawTx.accountList[instruction.Accounts[6]]
|
|
outputVault := tx.rawTx.accountList[instruction.Accounts[7]]
|
|
|
|
vault0 := instruction.Accounts[6]
|
|
vault1 := instruction.Accounts[7]
|
|
|
|
inputTokenMint := tx.rawTx.accountList[instruction.Accounts[10]]
|
|
outputTokenMint := tx.rawTx.accountList[instruction.Accounts[11]]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get input amount: %v", err)
|
|
}
|
|
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get output amount: %v", err)
|
|
}
|
|
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
userBase := getAccountBalanceAfterTx(tx.rawTx, user0)
|
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, user1)
|
|
|
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
|
|
|
var baseFound, quoteFound bool
|
|
var baseAmount, quoteAmount decimal.Decimal
|
|
for _, inner := range inners {
|
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if from.Equals(tokenIn) && to.Equals(inputVault) && !baseFound {
|
|
baseFound = true
|
|
baseAmount = decimal.NewFromUint64(amount)
|
|
} else if from.Equals(outputVault) && to.Equals(tokenOut) && !quoteFound {
|
|
quoteFound = true
|
|
quoteAmount = decimal.NewFromUint64(amount)
|
|
}
|
|
if baseFound && quoteFound {
|
|
break
|
|
}
|
|
}
|
|
if !baseFound || !quoteFound {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
|
}
|
|
offset[1] += 2
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCPMM,
|
|
Event: "sell",
|
|
Pool: market,
|
|
BaseMint: inputTokenMint,
|
|
QuoteMint: outputTokenMint,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
User: user,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|