Files
pump-parser/meteoradamm.go
2026-02-11 14:58:12 +08:00

487 lines
20 KiB
Go

package pump_parser
import (
"bytes"
"fmt"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
func metaoraDammParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(meteoraDammV2Program) {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm program instruction not found, offset, %d, %d", offset[0], offset[1])
}
decode := instruction.Data
if len(decode) < 8 {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm program instruction data too short, offset, %d, %d", offset[0], offset[1])
}
discriminator := *(*[8]byte)(decode[:8])
switch discriminator {
case meteoraDammV2InitializeCustomizablePoolDiscriminator,
meteoraDammV2InitializePoolWithDynamicConfig,
meteoraDammV2InitializePoolDiscriminator:
return meteoraDammV2InitializePoolParser(tx, instruction, innerInstructions, offset)
case meteoraDammV2SwapDiscriminator, meteoraDammV2SwapV2Discriminator:
return meteoraDammV2Swap(tx, instruction, innerInstructions, offset)
case meteoraDammV2AddLiquidityDiscriminator:
return meteoraDammV2AddLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDammV2RemoveLiquidityDiscriminator, meteoraDammV2RemoveAllLiquidityDiscriminator:
return meteoraDammV2RemoveLiquidityParser(tx, instruction, innerInstructions, offset)
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
var (
metaoraDammInitializePoolDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 228, 50, 246, 85, 203, 66, 134, 37}
meteoraDammSwapDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 189, 66, 51, 168, 38, 80, 117, 153}
// EvtLiquidityChange
meteoraDammAddLiquidityDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 197, 171, 78, 127, 224, 211, 87, 13}
meteoraDammRemoveLiquidityDiscriminator = []byte{228, 69, 165, 46, 81, 203, 154, 29, 197, 171, 78, 127, 224, 211, 87, 13}
)
type MetaoraDammDynamicFeeParameters struct {
BinStep uint16
BinStepU128 [16]byte
FilterPeriod uint16
DecayPeriod uint16
ReductionFactor uint16
MaxVolatilityAccumulator uint32
VariableFeeControl uint32
}
type MetaoraDammInitializePoolEvent struct {
Pool solana.PublicKey `json:"pool"`
TokenAMint solana.PublicKey `json:"tokenAMint"`
TokenBMint solana.PublicKey `json:"tokenBMint"`
Creator solana.PublicKey `json:"creator"`
Payer solana.PublicKey `json:"payer"`
AlphaVault solana.PublicKey `json:"alphaVault"`
//PoolFees *struct {
// BaseFee [30]byte
// DynamicFee *MetaoraDammDynamicFeeParameters `json:"dynamicFee"`
//} `json:"poolFees"`
//SqrtMinPrice [16]byte `json:"sqrtMinPrice"`
//SqrtMaxPrice [16]byte `json:"sqrtMaxPrice"`
//ActivationType uint8 `json:"activationType"`
//CollectFeeMode uint8 `json:"collectFeeMode"`
//Liquidity [16]byte `json:"liquidity"`
//SqrtPrice [16]byte `json:"sqrtPrice"`
//ActivationPoint uint64 `json:"activationPoint"`
//TokenAFlag uint8 `json:"tokenAFlag"`
//TokenBFlag uint8 `json:"tokenBFlag"`
//TokenAAmount uint64 `json:"tokenAAmount"`
//TokenBAmount uint64 `json:"tokenBAmount"`
//TotalAmountA uint64 `json:"totalAmountA"`
//TotalAmountB uint64 `json:"totalAmountB"`
//PoolType uint8 `json:"poolType"`
}
func meteoraDammV2InitializePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if len(instruction.Accounts) < 12 {
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
}
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("meta damm initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var loadedEvent bool
var initializePoolEvent MetaoraDammInitializePoolEvent
for i, innerInstruction := range inners {
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], metaoraDammInitializePoolDiscriminator) {
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&initializePoolEvent)
if err != nil {
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
return nil, offset, fmt.Errorf("failed to deserialize initialize pool event: %w", err)
}
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
loadedEvent = true
break
}
}
if !loadedEvent {
return nil, increaseOffset(offset), fmt.Errorf("failed to get initialize pool event")
}
baseVaultAccountIndex := instruction.Accounts[10]
quoteVaultAccountIndex := instruction.Accounts[11]
if bytes.Equal(instruction.Data[:8], meteoraDammV2InitializePoolWithDynamicConfig[:]) {
baseVaultAccountIndex = instruction.Accounts[11]
quoteVaultAccountIndex = instruction.Accounts[12]
} else if bytes.Equal(instruction.Data[:8], meteoraDammV2InitializeCustomizablePoolDiscriminator[:]) {
baseVaultAccountIndex = instruction.Accounts[9]
quoteVaultAccountIndex = instruction.Accounts[10]
}
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
swap := Swap{
Program: SolProgramMeteoraAmmV2,
Event: "create",
Pool: initializePoolEvent.Pool,
BaseMint: initializePoolEvent.TokenAMint,
QuoteMint: initializePoolEvent.TokenBMint,
BaseTokenProgram: baseVaultTokenBalance.ProgramIDAccount,
QuoteTokenProgram: quoteVaultTokenBalance.ProgramIDAccount,
Creator: initializePoolEvent.Creator,
BaseMintDecimals: uint8(baseVaultTokenBalance.UITokenAmount.Decimals),
QuoteMintDecimals: uint8(quoteVaultTokenBalance.UITokenAmount.Decimals),
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
User: tx.rawTx.accountList[instruction.Accounts[0]],
LpMint: tx.rawTx.accountList[instruction.Accounts[1]],
EntryContract: entryContract,
}
return []Swap{swap}, offset, nil
}
type meteoraDammSwapEvent struct {
Pool solana.PublicKey
TradeDirection uint8
CollectFeeMode uint8
HasReferral bool
Params *struct {
Amount0 uint64
Amount1 uint64
SwapMode uint8
}
SwapResult *struct {
IncludedFeeInputAmount uint64
ExcludedFeeInputAmount uint64
AmountLeft uint64
OutputAmount uint64
NextSqrtPrice [16]byte
TradingFee uint64
ProtocolFee uint64
PartnerFee uint64
ReferralFee uint64
}
IncludedTransferFeeAmountIn uint64
IncludedTransferFeeAmountOut uint64
ExcludedTransferFeeAmountOut uint64
CurrentTimestamp uint64
ReserveAAmount uint64
ReserveBAmount uint64
}
func meteoraDammV2Swap(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if len(instruction.Accounts) < 9 {
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
}
sourceAccountIndex := instruction.Accounts[2]
destinationAccountIndex := instruction.Accounts[3]
baseVaultAccountIndex := instruction.Accounts[4]
quoteVaultAccountIndex := instruction.Accounts[5]
tokenAMint := tx.rawTx.accountList[instruction.Accounts[6]]
tokenBMint := tx.rawTx.accountList[instruction.Accounts[7]]
payer := tx.rawTx.accountList[instruction.Accounts[8]]
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
baseMint := tokenAMint
quoteMint := tokenBMint
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
userInputTokenBalance := getAccountBalanceAfterTx(tx.rawTx, sourceAccountIndex)
userOutputTokenBalance := getAccountBalanceAfterTx(tx.rawTx, destinationAccountIndex)
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var loadedEvent bool
var swapEvent meteoraDammSwapEvent
for i, innerInstruction := range inners {
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammSwapDiscriminator) {
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&swapEvent)
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
if err != nil {
return nil, offset, fmt.Errorf("failed to deserialize swap event: %w", err)
}
loadedEvent = true
break
}
}
if !loadedEvent {
return nil, increaseOffset(offset), fmt.Errorf("failed to get swap event")
}
var baseAmount decimal.Decimal
var quoteAmount decimal.Decimal
var userBase decimal.Decimal
var userQuote decimal.Decimal
event := "buy"
if swapEvent.TradeDirection == 0 {
// A -> B
// sell base/A; buy quote/B
event = "sell"
userBase = userInputTokenBalance
userQuote = userOutputTokenBalance
baseAmount = decimal.NewFromUint64(swapEvent.SwapResult.IncludedFeeInputAmount)
quoteAmount = decimal.NewFromUint64(swapEvent.ExcludedTransferFeeAmountOut)
} else if swapEvent.TradeDirection == 1 {
// B -> A
// sell quote/B; buy base/A
userBase = userOutputTokenBalance
userQuote = userInputTokenBalance
baseAmount = decimal.NewFromUint64(swapEvent.ExcludedTransferFeeAmountOut)
quoteAmount = decimal.NewFromUint64(swapEvent.SwapResult.IncludedFeeInputAmount)
} else {
return nil, offset, fmt.Errorf("invalid trade direction")
}
return []Swap{
{
Program: SolProgramMeteoraAmmV2,
Event: event,
Pool: swapEvent.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: solana.PublicKey{},
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: payer,
BaseAmount: baseAmount,
QuoteAmount: quoteAmount,
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
}
type MeteoraDammV2LiquidityData struct {
LiquidityDelta [16]byte `json:"liquidityDelta"`
TokenAAmounthreshold uint64 `json:"tokenAAmounthreshold"`
TokenBAmounthreshold uint64 `json:"tokenBAmounthreshold"`
}
type MeteoraDammV2AddLiquidityEvent struct {
Pool solana.PublicKey `json:"pool"`
Position solana.PublicKey `json:"position"`
Owner solana.PublicKey `json:"owner"`
Params *MeteoraDammV2LiquidityData `json:"params"`
TokenAAmount uint64 `json:"tokenAAmount"`
TokenBAmount uint64 `json:"tokenBAmount"`
TotalAmountA uint64 `json:"totalAmountA"`
TotalAmountB uint64 `json:"totalAmountB"`
}
type MeteoraDammV2RemoveLiquidityEvent struct {
Pool solana.PublicKey `json:"pool"`
Position solana.PublicKey `json:"position"`
Owner solana.PublicKey `json:"owner"`
Params *MeteoraDammV2LiquidityData `json:"params"`
TokenAAmount uint64 `json:"tokenAAmount"`
TokenBAmount uint64 `json:"tokenBAmount"`
}
func meteoraDammV2AddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if len(instruction.Accounts) < 8 {
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
}
tokenAMint := tx.rawTx.accountList[instruction.Accounts[6]]
tokenBMint := tx.rawTx.accountList[instruction.Accounts[7]]
baseVaultAccountIndex := instruction.Accounts[4]
quoteVaultAccountIndex := instruction.Accounts[5]
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var loadedEvent bool
var liquidityEvent MeteoraDammV2AddLiquidityEvent
for i, innerInstruction := range inners {
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammAddLiquidityDiscriminator[:]) {
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&liquidityEvent)
if err != nil {
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
return nil, offset, fmt.Errorf("failed to deserialize add liquidity event: %w", err)
}
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
loadedEvent = true
break
}
}
if !loadedEvent {
return nil, increaseOffset(offset), fmt.Errorf("failed to get add liquidity event")
}
swap := Swap{
Program: SolProgramMeteoraDLMM,
Event: "add_liquidity",
Pool: liquidityEvent.Pool,
BaseMint: tokenAMint,
QuoteMint: tokenBMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: liquidityEvent.Owner,
BaseAmount: decimal.NewFromUint64(liquidityEvent.TokenAAmount),
QuoteAmount: decimal.NewFromUint64(liquidityEvent.TokenBAmount),
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
EntryContract: entryContract,
}
return []Swap{swap}, offset, nil
}
func meteoraDammV2RemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if len(instruction.Accounts) < 8 {
return nil, increaseOffset(offset), fmt.Errorf("invalid instruction accounts length")
}
tokenAMint := tx.rawTx.accountList[instruction.Accounts[7]]
tokenBMint := tx.rawTx.accountList[instruction.Accounts[8]]
baseVaultAccountIndex := instruction.Accounts[5]
quoteVaultAccountIndex := instruction.Accounts[6]
baseVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get base vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
quoteVaultTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultAccountIndex)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("metaora damm get quote vault token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
}
baseReserve, _ := decimal.NewFromString(baseVaultTokenBalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteVaultTokenBalance.UITokenAmount.Amount)
baseTokenProgram := baseVaultTokenBalance.ProgramIDAccount
quoteTokenProgram := quoteVaultTokenBalance.ProgramIDAccount
baseMintDecimals := uint8(baseVaultTokenBalance.UITokenAmount.Decimals)
quoteMintDecimals := uint8(quoteVaultTokenBalance.UITokenAmount.Decimals)
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("meta damm get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var loadedEvent bool
var liquidityEvent MeteoraDammV2RemoveLiquidityEvent
for i, innerInstruction := range inners {
if innerInstruction.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstruction.Data) >= 16 && bytes.Equal(innerInstruction.Data[:16], meteoraDammRemoveLiquidityDiscriminator[:]) {
err := agbinary.NewBorshDecoder(innerInstruction.Data[16:]).Decode(&liquidityEvent)
if err != nil {
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
return nil, offset, fmt.Errorf("failed to deserialize remove liquidity event: %w", err)
}
if offset[1] == 0 {
offset[0] += 1
} else {
offset[1] = uint(i) + 1 + prefixLen
}
loadedEvent = true
break
}
}
if !loadedEvent {
return nil, increaseOffset(offset), fmt.Errorf("failed to get remove liquidity event")
}
swap := Swap{
Program: SolProgramMeteoraDLMM,
Event: "remove_liquidity",
Pool: liquidityEvent.Pool,
BaseMint: tokenAMint,
QuoteMint: tokenBMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: liquidityEvent.Owner,
BaseAmount: decimal.NewFromUint64(liquidityEvent.TokenAAmount),
QuoteAmount: decimal.NewFromUint64(liquidityEvent.TokenBAmount),
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
EntryContract: entryContract,
}
return []Swap{swap}, offset, nil
}