2026-02-09 14:46:19 +08:00
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 ,
} ,
}
2026-04-16 14:24:14 +08:00
if swapEvent . Params != nil {
swaps [ 0 ] . SetSwapAmountInfo (
SwapModeExactIn ,
decimal . NewFromUint64 ( swapEvent . Params . AmountIn ) ,
decimal . NewFromUint64 ( swapEvent . Params . MinimumAmountOut ) ,
)
}
2026-02-09 14:46:19 +08:00
return swaps , offset , nil
}