480 lines
19 KiB
Go
480 lines
19 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 Bonding Curve 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]
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|