397 lines
17 KiB
Go
397 lines
17 KiB
Go
package pump_parser
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
agbinary "github.com/gagliardetto/binary"
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type MetaoraBcEvtInitializePool struct {
|
|
Pool solana.PublicKey
|
|
Config solana.PublicKey
|
|
Creator solana.PublicKey
|
|
BaseMint solana.PublicKey
|
|
//PoolType uint8
|
|
//ActivationPoint uint64
|
|
}
|
|
|
|
type MetaoraBcSwapEvent struct {
|
|
Pool solana.PublicKey `json:"pool"`
|
|
Config solana.PublicKey `json:"config"`
|
|
TradeDirection uint8 `json:"tradeDirection"`
|
|
HasReferral bool `json:"hasReferral"`
|
|
Params *struct {
|
|
AmountIn uint64 `json:"amountIn"`
|
|
MinimumAmountOut uint64 `json:"minimumAmountOut"`
|
|
} `json:"params"`
|
|
SwapResult *struct {
|
|
ActualInputAmount uint64 `json:"actualInputAmount"`
|
|
OutputAmount uint64 `json:"outputAmount"`
|
|
NextSqrtPrice [16]byte `json:"nextSqrtPrice"`
|
|
TradingFee uint64 `json:"tradingFee"`
|
|
ProtocolFee uint64 `json:"protocolFee"`
|
|
ReferralFee uint64 `json:"referralFee"`
|
|
} `json:"swapResult"`
|
|
AmountIn uint64 `json:"amountIn"`
|
|
CurrentTimestamp uint64 `json:"currentTimestamp"`
|
|
}
|
|
|
|
func metaoraBcParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(metaoraBcProgramID) {
|
|
return nil, increaseOffset(offset), fmt.Errorf("metaora Bonding Curve 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 Bonding Curve program instruction data too short, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
|
|
discriminator := *(*[8]byte)(decode[:8])
|
|
|
|
switch discriminator {
|
|
case metaoraBcInitialize2022PoolDiscriminator,
|
|
metaoraBcInitializedPoolDiscriminator:
|
|
return metaBcInitializePoolParser(tx, instruction, innerInstructions, offset)
|
|
case metaoraBcMigrateMeteoraDammDiscriminator:
|
|
return metaBcMigrateParser(tx, instruction, innerInstructions, offset)
|
|
case metaoraBcMigrateMeteoraDammV2Discriminator:
|
|
return metaBcMigrateV2Parser(tx, instruction, innerInstructions, offset)
|
|
case metaoraBcSwapDiscriminator:
|
|
return metaBcSwapParser(tx, instruction, innerInstructions, offset)
|
|
case metaoraBcSwapV2Discriminator:
|
|
return metaBcSwapParser(tx, instruction, innerInstructions, offset)
|
|
default:
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
}
|
|
|
|
type MetaoraCreateData struct {
|
|
Name string
|
|
Symbol string
|
|
Uri string
|
|
}
|
|
|
|
func metaBcInitializePoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 14 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool not enough accounts, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
var createData MetaoraCreateData
|
|
err := agbinary.NewBorshDecoder(instruction.Data[8:]).Decode(&createData)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse create data error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
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])
|
|
}
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool get base token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[7])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool get quote token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
baseReserve, err := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse base reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteReserve, err := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool parse quote reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
var user solana.PublicKey
|
|
if bytes.Equal(instruction.Data[:8], metaoraBcInitialize2022PoolDiscriminator[:]) {
|
|
user = tx.rawTx.accountList[instruction.Accounts[8]]
|
|
} else if bytes.Equal(instruction.Data[:8], metaoraBcInitializedPoolDiscriminator[:]) {
|
|
user = tx.rawTx.accountList[instruction.Accounts[10]]
|
|
} else {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool unknown discriminator, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
|
var (
|
|
pool solana.PublicKey
|
|
baseMint solana.PublicKey
|
|
creator solana.PublicKey
|
|
totalSupply *decimal.Decimal
|
|
)
|
|
for innerIndex, innerInstr := range inners {
|
|
if tx.rawTx.accountList[innerInstr.ProgramIDIndex].Equals(baseMint) &&
|
|
len(innerInstr.Data) >= 9 && innerInstr.Data[0] == 7 &&
|
|
len(innerInstr.Accounts) == 3 && tx.rawTx.accountList[innerInstr.Accounts[0]].Equals(baseMint) &&
|
|
innerInstr.Accounts[1] == instruction.Accounts[6] {
|
|
supply := decimal.NewFromUint64(binary.LittleEndian.Uint64(innerInstr.Data[1:9]))
|
|
totalSupply = &supply
|
|
}
|
|
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
|
|
len(innerInstr.Data) >= 16 &&
|
|
bytes.Equal(innerInstr.Data[0:8], pumpEventDiscriminator[:]) &&
|
|
bytes.Equal(innerInstr.Data[8:16], metaoraBcEventInitializePoolDiscriminator[:]) {
|
|
|
|
var event MetaoraBcEvtInitializePool
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initialize pool deserialize event error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
pool = event.Pool
|
|
baseMint = event.BaseMint
|
|
creator = event.Creator
|
|
break
|
|
}
|
|
}
|
|
if pool.IsZero() {
|
|
return nil, offset, fmt.Errorf("meta Bonding Curve initialize pool event not found, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
|
|
tx.Token[baseMint] = TokenMeta{
|
|
Mint: baseMint,
|
|
TokenProgram: baseTokenProgram,
|
|
Decimals: baseMintDecimals,
|
|
Name: createData.Name,
|
|
Symbol: createData.Symbol,
|
|
Url: createData.Uri,
|
|
TotalSupply: totalSupply,
|
|
}
|
|
return []Swap{
|
|
{
|
|
Program: SolProgramMeteoraBondingCurve,
|
|
Event: "create",
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: creator,
|
|
BaseMintDecimals: baseMintDecimals,
|
|
QuoteMintDecimals: quoteMintDecimals,
|
|
User: user,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
EntryContract: entryContract,
|
|
},
|
|
}, offset, nil
|
|
}
|
|
|
|
func metaBcMigrateV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 25 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
|
}
|
|
|
|
baseVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[17])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get base vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[18])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get quote vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramMeteoraBondingCurve,
|
|
Event: "migrate",
|
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
BaseMint: tx.rawTx.accountList[instruction.Accounts[13]],
|
|
QuoteMint: tx.rawTx.accountList[instruction.Accounts[14]],
|
|
BaseTokenProgram: tx.rawTx.accountList[instruction.Accounts[20]],
|
|
QuoteTokenProgram: tx.rawTx.accountList[instruction.Accounts[21]],
|
|
BaseMintDecimals: uint8(baseVaultBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteVaultBalance.UITokenAmount.Decimals),
|
|
User: tx.rawTx.accountList[instruction.Accounts[19]],
|
|
//BaseAmount: decimal.Decimal{},
|
|
//QuoteAmount: decimal.Decimal{},
|
|
//BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
|
//QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
|
MigrateTopProgram: meteoraDammV2Program,
|
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[4]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
return swaps, offset, nil
|
|
}
|
|
|
|
func metaBcMigrateParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 23 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for migrate instruction")
|
|
}
|
|
|
|
baseVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[17])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get base vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteVaultBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[18])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve migrate get quote vault balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramMeteoraBondingCurve,
|
|
Event: "migrate",
|
|
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
|
|
BaseMint: tx.rawTx.accountList[instruction.Accounts[7]],
|
|
QuoteMint: tx.rawTx.accountList[instruction.Accounts[8]],
|
|
BaseTokenProgram: baseVaultBalance.ProgramIDAccount,
|
|
QuoteTokenProgram: baseVaultBalance.ProgramIDAccount,
|
|
BaseMintDecimals: uint8(baseVaultBalance.UITokenAmount.Decimals),
|
|
QuoteMintDecimals: uint8(quoteVaultBalance.UITokenAmount.Decimals),
|
|
User: tx.rawTx.accountList[instruction.Accounts[22]],
|
|
//BaseAmount: decimal.Decimal{},
|
|
//QuoteAmount: decimal.Decimal{},
|
|
//BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
|
//QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
|
MigrateTopProgram: metaoraPoolProgramID,
|
|
MigrateToPool: tx.rawTx.accountList[instruction.Accounts[4]],
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
return swaps, offset, nil
|
|
}
|
|
|
|
func metaBcSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
if len(instruction.Accounts) < 15 {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap not enough accounts, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
|
|
userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[3])
|
|
userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[4])
|
|
inputToken := tx.rawTx.accountList[instruction.Accounts[3]]
|
|
outputToken := tx.rawTx.accountList[instruction.Accounts[4]]
|
|
|
|
baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap get base token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap get quote token balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
baseReserve, err := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap parse base reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
quoteReserve, err := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve swap parse quote reserve error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
baseTokenProgram := baseTokenBalance.ProgramIDAccount
|
|
quoteTokenProgram := quoteTokenBalance.ProgramIDAccount
|
|
baseMintDecimals := uint8(baseTokenBalance.UITokenAmount.Decimals)
|
|
quoteMintDecimals := uint8(quoteTokenBalance.UITokenAmount.Decimals)
|
|
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 (
|
|
swapEvent MetaoraBcSwapEvent
|
|
eventLoaded bool
|
|
event string
|
|
)
|
|
for innerIndex, innerInstr := range inners {
|
|
from, to, _, err := parseTokenTransfer(tx.rawTx, innerInstr)
|
|
if err == nil {
|
|
if from.Equals(inputToken) && to.Equals(tx.rawTx.accountList[quoteTokenBalance.AccountIndex]) {
|
|
event = "buy"
|
|
} else if from.Equals(tx.rawTx.accountList[quoteTokenBalance.AccountIndex]) && to.Equals(outputToken) {
|
|
event = "sell"
|
|
}
|
|
}
|
|
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex && len(innerInstr.Data) >= 16 &&
|
|
bytes.Equal(innerInstr.Data[0:8], pumpEventDiscriminator[:]) &&
|
|
bytes.Equal(innerInstr.Data[8:16], metaoraBcEventSwapDiscriminator[:]) {
|
|
if offset[1] == 0 {
|
|
offset[0] += 1
|
|
} else {
|
|
offset[1] = uint(innerIndex) + 1 + prefixLen
|
|
}
|
|
err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&swapEvent)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve pool swap event deserialize event error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
eventLoaded = true
|
|
break
|
|
}
|
|
}
|
|
if !eventLoaded {
|
|
return nil, offset, fmt.Errorf("meta Bonding Curve swap event not found, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
baseMint := tx.rawTx.accountList[instruction.Accounts[7]]
|
|
quoteMint := tx.rawTx.accountList[instruction.Accounts[8]]
|
|
user := tx.rawTx.accountList[instruction.Accounts[9]]
|
|
pool := tx.rawTx.accountList[instruction.Accounts[2]]
|
|
var (
|
|
baseMintAmount decimal.Decimal
|
|
quoteMintAmount decimal.Decimal
|
|
)
|
|
if swapEvent.TradeDirection == 0 {
|
|
// A -> B
|
|
if event == "sell" {
|
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
|
} else {
|
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
|
}
|
|
|
|
} else {
|
|
// B -> A
|
|
if event == "buy" {
|
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
|
} else {
|
|
baseMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.ActualInputAmount)
|
|
quoteMintAmount = decimal.NewFromUint64(swapEvent.SwapResult.OutputAmount)
|
|
}
|
|
}
|
|
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramMeteoraBondingCurve,
|
|
Event: event,
|
|
Pool: pool,
|
|
BaseMint: baseMint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: solana.PublicKey{},
|
|
BaseMintDecimals: baseMintDecimals,
|
|
QuoteMintDecimals: quoteMintDecimals,
|
|
User: user,
|
|
BaseAmount: baseMintAmount,
|
|
QuoteAmount: quoteMintAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
if swapEvent.Params != nil {
|
|
swaps[0].SetSwapAmountInfo(
|
|
SwapModeExactIn,
|
|
decimal.NewFromUint64(swapEvent.Params.AmountIn),
|
|
decimal.NewFromUint64(swapEvent.Params.MinimumAmountOut),
|
|
)
|
|
}
|
|
return swaps, offset, nil
|
|
}
|