388 lines
15 KiB
Go
388 lines
15 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
func decodeRaydiumClmmSwapArgs(data []byte) (amountSpecified uint64, otherAmountThreshold uint64, swapMode SwapMode, err error) {
|
|
if len(data) < 41 {
|
|
return 0, 0, SwapModeUnknown, fmt.Errorf("raydium clmm swap instruction data too short")
|
|
}
|
|
amountSpecified = binary.LittleEndian.Uint64(data[8:16])
|
|
otherAmountThreshold = binary.LittleEndian.Uint64(data[16:24])
|
|
isBaseInput := data[40] != 0
|
|
swapMode = SwapModeExactOut
|
|
if isBaseInput {
|
|
swapMode = SwapModeExactIn
|
|
}
|
|
return amountSpecified, otherAmountThreshold, swapMode, nil
|
|
}
|
|
|
|
func raydiumClmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(raydiumClmmProgramID) {
|
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumClmm instruction not found, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
decode := instruction.Data
|
|
if len(decode) < 8 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("raydiumClmm program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
|
|
switch discriminator {
|
|
case raydiumClmmCreatePoolDiscriminator:
|
|
return raydiumClmmCreatePoolParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumClmmIncreaseLiquidityDiscriminator, raydiumClmmIncreaseLiquidityV2Discriminator, raydiumClmmOpenPositionDiscriminator, raydiumClmmOpenPositionV2Discriminator, raydiumClmmOpenPositionWithToken22NftDiscriminator:
|
|
return raydiumClmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumClmmDecreaseLiquidityDiscriminator, raydiumClmmDecreaseLiquidityV2Discriminator:
|
|
return raydiumClmmDecreaseLiquidityParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumClmmCollectFundFeeDiscriminator, raydiumClmmCollectProtocolFeeDiscriminator:
|
|
return raydiumClmmCollectFeeParser(tx, instruction, innerInstructions, offset)
|
|
case raydiumClmmSwapDiscriminator, raydiumClmmSwapV2Discriminator:
|
|
return raydiumClmmSwapParser(tx, instruction, innerInstructions, offset)
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
}
|
|
|
|
func raydiumClmmCreatePoolParser(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 raydiumClmm create pool instruction, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
|
|
pool := tx.rawTx.accountList[instruction.Accounts[2]]
|
|
creator := tx.rawTx.accountList[instruction.Accounts[0]]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
|
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[6])
|
|
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] += 9
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCLMM,
|
|
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,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumClmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
|
var (
|
|
accountMin int
|
|
market solana.PublicKey
|
|
//token0 solana.PublicKey
|
|
//token1 solana.PublicKey
|
|
lpToken solana.PublicKey
|
|
vault0 int
|
|
vault1 int
|
|
)
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
|
|
switch discriminator {
|
|
case raydiumClmmIncreaseLiquidityDiscriminator:
|
|
accountMin = 12
|
|
case raydiumClmmIncreaseLiquidityV2Discriminator:
|
|
accountMin = 15
|
|
case raydiumClmmOpenPositionDiscriminator:
|
|
accountMin = 19
|
|
case raydiumClmmOpenPositionV2Discriminator:
|
|
accountMin = 22
|
|
case raydiumClmmOpenPositionWithToken22NftDiscriminator:
|
|
accountMin = 20
|
|
default:
|
|
return nil, increaseOffset(offset), fmt.Errorf("invalid discriminator")
|
|
}
|
|
|
|
if len(instruction.Accounts) < accountMin {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for raydiumClmm add liquidity instruction, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
switch discriminator {
|
|
case raydiumClmmIncreaseLiquidityDiscriminator, raydiumClmmIncreaseLiquidityV2Discriminator:
|
|
market = tx.rawTx.accountList[instruction.Accounts[2]]
|
|
vault0 = instruction.Accounts[9]
|
|
vault1 = instruction.Accounts[10]
|
|
case raydiumClmmOpenPositionDiscriminator, raydiumClmmOpenPositionV2Discriminator:
|
|
market = tx.rawTx.accountList[instruction.Accounts[5]]
|
|
vault0 = instruction.Accounts[12]
|
|
vault1 = instruction.Accounts[13]
|
|
lpToken = tx.rawTx.accountList[instruction.Accounts[2]]
|
|
case raydiumClmmOpenPositionWithToken22NftDiscriminator:
|
|
market = tx.rawTx.accountList[instruction.Accounts[4]]
|
|
vault0 = instruction.Accounts[11]
|
|
vault1 = instruction.Accounts[12]
|
|
lpToken = tx.rawTx.accountList[instruction.Accounts[2]]
|
|
}
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, 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, 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] += 2
|
|
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCLMM,
|
|
Event: "add_liquidity",
|
|
Pool: market,
|
|
BaseMint: baseTokenBalance.MintAccount,
|
|
QuoteMint: quoteTokenBalance.MintAccount,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
LpMint: lpToken,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumClmmDecreaseLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
|
var (
|
|
accountMin int
|
|
market solana.PublicKey
|
|
vault0 int
|
|
vault1 int
|
|
)
|
|
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
if discriminator == raydiumClmmDecreaseLiquidityDiscriminator {
|
|
accountMin = 14
|
|
} else if discriminator == raydiumClmmDecreaseLiquidityV2Discriminator {
|
|
accountMin = 16
|
|
} else {
|
|
return nil, increaseOffset(offset), fmt.Errorf("invalid discriminator")
|
|
}
|
|
if len(instruction.Accounts) < accountMin {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for decrease liquidity instruction")
|
|
}
|
|
market = tx.rawTx.accountList[instruction.Accounts[3]]
|
|
vault0 = instruction.Accounts[5]
|
|
vault1 = instruction.Accounts[6]
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, 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, 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] += 2
|
|
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCLMM,
|
|
Event: "remove_liquidity",
|
|
Pool: market,
|
|
BaseMint: baseTokenBalance.MintAccount,
|
|
QuoteMint: quoteTokenBalance.MintAccount,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumClmmCollectFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 11 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for CollectFeeParser instruction")
|
|
}
|
|
pool := tx.rawTx.accountList[instruction.Accounts[1]]
|
|
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
vault0 := instruction.Accounts[3]
|
|
vault1 := instruction.Accounts[4]
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, 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, 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] += 2
|
|
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramRaydiumCLMM,
|
|
Event: "remove_liquidity",
|
|
Pool: pool,
|
|
BaseMint: baseTokenBalance.MintAccount,
|
|
QuoteMint: quoteTokenBalance.MintAccount,
|
|
BaseTokenProgram: baseTokenBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals),
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func raydiumClmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
discriminator := *(*[8]byte)(instruction.Data[:8])
|
|
var (
|
|
pool solana.PublicKey
|
|
|
|
accountMin int
|
|
tokenInVault int
|
|
tokenOutVault int
|
|
userTokenInAccount int
|
|
userTokenOutAccount int
|
|
)
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
amountSpecified, otherAmountThreshold, swapMode, err := decodeRaydiumClmmSwapArgs(instruction.Data)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), err
|
|
}
|
|
if discriminator == raydiumClmmSwapDiscriminator {
|
|
accountMin = 9
|
|
} else if discriminator == raydiumClmmSwapV2Discriminator {
|
|
accountMin = 13
|
|
} else {
|
|
return nil, increaseOffset(offset), fmt.Errorf("invalid discriminator")
|
|
}
|
|
if len(instruction.Accounts) < accountMin {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for swap instruction")
|
|
}
|
|
|
|
pool = tx.rawTx.accountList[instruction.Accounts[2]]
|
|
userTokenInAccount = instruction.Accounts[3]
|
|
userTokenOutAccount = instruction.Accounts[4]
|
|
tokenInVault = instruction.Accounts[5]
|
|
tokenOutVault = instruction.Accounts[6]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, tokenInVault)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get tokenIn vault balance after tx: %w", err)
|
|
}
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, tokenOutVault)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get tokenOut vault balance after tx: %w", err)
|
|
}
|
|
baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
|
baseMint := baseTokenBalance.MintAccount
|
|
quoteMint := quoteTokenBalance.MintAccount
|
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
|
|
|
userBase := getAccountBalanceAfterTx(tx.rawTx, userTokenInAccount)
|
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, userTokenOutAccount)
|
|
|
|
inners, err := getInnerInstructions(innerInstructions, offset[1])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to get inner instructions: %w", err)
|
|
}
|
|
if len(inners) < 2 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for swap instruction")
|
|
}
|
|
baseVaultAccount := tx.rawTx.accountList[tokenInVault]
|
|
quoteVaultAccount := tx.rawTx.accountList[tokenOutVault]
|
|
userBaseAccount := tx.rawTx.accountList[userTokenInAccount]
|
|
userQuoteAccount := tx.rawTx.accountList[userTokenOutAccount]
|
|
var baseAmount, quoteAmount decimal.Decimal
|
|
var baseFound, quoteFound bool
|
|
for i := 0; i < 2; i++ {
|
|
inner := inners[i]
|
|
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to parse token transfer: %w", err)
|
|
}
|
|
if from.Equals(userBaseAccount) && to.Equals(baseVaultAccount) && !baseFound {
|
|
baseAmount = decimal.NewFromUint64(amount)
|
|
baseFound = true
|
|
} else if from.Equals(quoteVaultAccount) && to.Equals(userQuoteAccount) && !quoteFound {
|
|
quoteAmount = decimal.NewFromUint64(amount)
|
|
quoteFound = true
|
|
}
|
|
}
|
|
if !baseFound || !quoteFound {
|
|
return nil, increaseOffset(offset), fmt.Errorf("failed to find token transfer in inner instructions")
|
|
}
|
|
|
|
offset[1] += 2
|
|
|
|
swap := Swap{
|
|
Program: SolProgramRaydiumCLMM,
|
|
Event: "sell",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: baseMintDecimals,
|
|
QuoteMintDecimals: quoteMintDecimals,
|
|
User: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
}
|
|
swap.SetSwapAmountInfo(swapMode, decimal.NewFromUint64(amountSpecified), decimal.NewFromUint64(otherAmountThreshold))
|
|
return []Swap{swap}, offset, nil
|
|
|
|
}
|