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, }, } return swaps, offset, nil }