2025-11-20 17:56:45 +08:00
package pump_parser
import (
"bytes"
"fmt"
agbinary "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
func increaseOffset ( offset [ 2 ] uint ) [ 2 ] uint {
if offset [ 1 ] == 0 {
return [ 2 ] uint { offset [ 0 ] + 1 , offset [ 1 ] }
}
return [ 2 ] uint { offset [ 0 ] , offset [ 1 ] + 1 }
}
// pumpParser // routes pump program instructions to their respective parsers,
// offset is [outerIndex, innerIndex] index of instructions in the transaction,
// if innerIndex == 0 this is outer instruction,if it's an inner instruction, outerIndex is the index of the parent instruction.
2025-11-21 12:01:44 +08:00
func pumpParser ( tx * Tx , instruction Instruction , innerInstructions InnerInstructions , offset [ 2 ] uint ) ( [ ] Swap , [ 2 ] uint , error ) {
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
if ! tx . rawTx . accountList [ instruction . ProgramIDIndex ] . Equals ( pumpProgram ) {
2025-11-20 17:56:45 +08:00
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump program instruction not found, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
decode := instruction . Data
if len ( decode ) < 8 {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump program instruction data too short, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
discriminator := * ( * [ 8 ] byte ) ( decode [ : 8 ] )
switch discriminator {
2026-05-08 11:21:30 +08:00
case pumpBuyExactSolInDiscriminator , pumpBuyDiscriminator , pumpBuyV2Discriminator , pumpBuyExactQuoteInV2Discriminator , pumpSellDiscriminator , pumpSellV2Discriminator :
2026-02-26 16:11:34 +08:00
if tx . Err != nil {
return failedTxBuyOrSellParser ( tx , instruction , innerInstructions , offset )
}
2025-11-21 12:01:44 +08:00
return BuyOrSellParser ( tx , instruction , innerInstructions , offset )
2025-11-20 17:56:45 +08:00
case pumpCreateDiscriminator , pumpCreateV2Discriminator :
2026-02-26 16:11:34 +08:00
if tx . Err != nil {
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
2025-11-21 12:01:44 +08:00
return CreateParser ( tx , instruction , innerInstructions , offset )
2026-05-08 11:21:30 +08:00
case pumpMigrateDiscriminator , pumpMigrateV2Discriminator :
2026-02-26 16:11:34 +08:00
if tx . Err != nil {
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
2025-11-21 12:01:44 +08:00
return MigrateParser ( tx , instruction , innerInstructions , offset )
2025-11-20 17:56:45 +08:00
default :
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
}
type PumpCreateData struct {
Discriminator uint64
Name string
Symbol string
Uri string
Creator solana . PublicKey
}
type PumpCreateV2Data struct {
Discriminator uint64
Name string
Symbol string
Uri string
Creator solana . PublicKey
IsMayhem bool
}
type PumpCreateEvent struct {
Name string
Symbol string
Uri string
Mint solana . PublicKey
BondingCurve solana . PublicKey
User solana . PublicKey
Creator solana . PublicKey
Timestamp int64
VirtualTokenReserves uint64
VirtualSolReserves uint64
RealTokenReserves uint64
TokenTotalSupply uint64
TokenProgram solana . PublicKey
IsMayhemMode bool
2026-02-27 01:43:07 +08:00
IsCashbackEnabled bool
2026-05-08 11:21:30 +08:00
QuoteMint solana . PublicKey
VirtualQuoteReserves uint64
}
type pumpCreateEventLegacy struct {
Name string
Symbol string
Uri string
Mint solana . PublicKey
BondingCurve solana . PublicKey
User solana . PublicKey
Creator solana . PublicKey
Timestamp int64
VirtualTokenReserves uint64
VirtualSolReserves uint64
RealTokenReserves uint64
TokenTotalSupply uint64
TokenProgram solana . PublicKey
IsMayhemMode bool
IsCashbackEnabled bool
}
func decodePumpCreateEvent ( data [ ] byte ) ( PumpCreateEvent , error ) {
var event PumpCreateEvent
if err := agbinary . NewBorshDecoder ( data ) . Decode ( & event ) ; err == nil {
return event , nil
}
var legacy pumpCreateEventLegacy
if err := agbinary . NewBorshDecoder ( data ) . Decode ( & legacy ) ; err != nil {
return PumpCreateEvent { } , err
}
return PumpCreateEvent {
Name : legacy . Name ,
Symbol : legacy . Symbol ,
Uri : legacy . Uri ,
Mint : legacy . Mint ,
BondingCurve : legacy . BondingCurve ,
User : legacy . User ,
Creator : legacy . Creator ,
Timestamp : legacy . Timestamp ,
VirtualTokenReserves : legacy . VirtualTokenReserves ,
VirtualSolReserves : legacy . VirtualSolReserves ,
RealTokenReserves : legacy . RealTokenReserves ,
TokenTotalSupply : legacy . TokenTotalSupply ,
TokenProgram : legacy . TokenProgram ,
IsMayhemMode : legacy . IsMayhemMode ,
IsCashbackEnabled : legacy . IsCashbackEnabled ,
} , nil
2025-11-20 17:56:45 +08:00
}
2025-11-21 12:01:44 +08:00
func CreateParser ( tx * Tx , instr Instruction , innerInstructions InnerInstructions , offset [ 2 ] uint ) ( [ ] Swap , [ 2 ] uint , error ) {
result := tx . rawTx
2025-11-20 17:56:45 +08:00
var entryContract = result . accountList [ result . Transaction . Message . Instructions [ offset [ 0 ] ] . ProgramIDIndex ]
var programIndex = instr . ProgramIDIndex
var err error
var createEvent PumpCreateEvent
var prefixLen = offset [ 1 ]
inners , err := getInnerInstructions ( innerInstructions , prefixLen )
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump create get inner instructions error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
for innerIndex , innerInstr := range inners {
if innerInstr . ProgramIDIndex == programIndex && bytes . Equal ( innerInstr . Data [ : 8 ] , pumpEventDiscriminator [ : ] ) && bytes . Equal ( innerInstr . Data [ 8 : 16 ] , pumpCreateEventDiscriminator [ : ] ) {
2026-05-08 11:21:30 +08:00
createEvent , err = decodePumpCreateEvent ( innerInstr . Data [ 16 : ] )
2025-11-20 17:56:45 +08:00
if offset [ 1 ] == 0 {
offset [ 0 ] += 1
} else {
2026-02-09 14:46:19 +08:00
offset [ 1 ] = uint ( innerIndex ) + 1 + prefixLen
2025-11-20 17:56:45 +08:00
}
if err != nil {
return nil , offset , fmt . Errorf ( "pump create event decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
break
}
}
if createEvent == ( PumpCreateEvent { } ) {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump create event not found, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
userIndex := 0
if bytes . HasPrefix ( instr . Data , pumpCreateV2Discriminator [ : ] ) {
2026-05-28 10:23:56 +08:00
if len ( instr . Accounts ) < 6 {
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
2025-11-20 17:56:45 +08:00
userIndex = instr . Accounts [ 5 ]
} else if bytes . HasPrefix ( instr . Data , pumpCreateDiscriminator [ : ] ) {
2026-05-28 10:23:56 +08:00
if len ( instr . Accounts ) < 8 {
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
2025-11-20 17:56:45 +08:00
userIndex = instr . Accounts [ 7 ]
2026-05-28 10:23:56 +08:00
} else {
return nil , increaseOffset ( offset ) , InstructionIgnoredError
2025-11-20 17:56:45 +08:00
}
userBase := getAccountBalanceAfterTx ( result , userIndex )
userQuote , _ := GetSolAfterTx ( result , userIndex )
2026-05-08 11:21:30 +08:00
quoteMint , quoteTokenProgram , quoteDecimals := pumpCreateQuoteAccounts ( result , instr , createEvent )
userQuoteBalance := decimal . NewFromUint64 ( userQuote )
if ! quoteMint . IsZero ( ) && ! quoteMint . Equals ( wSolMint ) {
userQuoteBalance = GetTokenBalanceAfterTx ( result , userIndex , quoteTokenProgram , quoteMint )
}
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
totalSupply := decimal . NewFromUint64 ( createEvent . TokenTotalSupply ) . Div ( decimal . New ( 1 , 6 ) )
tx . Token [ createEvent . Mint ] = TokenMeta {
Mint : createEvent . Mint ,
TokenProgram : createEvent . TokenProgram ,
Decimals : 6 ,
Name : createEvent . Name ,
Symbol : createEvent . Symbol ,
Url : createEvent . Uri ,
TotalSupply : & totalSupply ,
}
2025-11-20 17:56:45 +08:00
return [ ] Swap {
{
Program : SolProgramPump ,
Event : "create" ,
Pool : createEvent . BondingCurve ,
BaseMint : createEvent . Mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
2025-11-20 17:56:45 +08:00
BaseTokenProgram : createEvent . TokenProgram ,
2026-05-08 11:21:30 +08:00
QuoteTokenProgram : quoteTokenProgram ,
2025-11-20 17:56:45 +08:00
Creator : createEvent . Creator ,
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : quoteDecimals ,
2025-11-20 17:56:45 +08:00
User : createEvent . User ,
BaseAmount : decimal . Zero ,
QuoteAmount : decimal . Zero ,
BaseReserve : decimal . NewFromUint64 ( createEvent . RealTokenReserves ) ,
QuoteReserve : decimal . Zero ,
Mayhem : createEvent . IsMayhemMode ,
2026-02-27 01:43:07 +08:00
Cashback : createEvent . IsCashbackEnabled ,
2025-11-20 17:56:45 +08:00
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuoteBalance ,
2025-11-20 17:56:45 +08:00
EntryContract : entryContract ,
} ,
} , offset , nil
}
type PumpTradeEvent struct {
Mint solana . PublicKey
SolAmount uint64
TokenAmount uint64
IsBuy bool
User solana . PublicKey
Timestamp int64
VirtualSolReserves uint64
VirtualTokenReserves uint64
RealSolReserves uint64
RealTokenReserves uint64
FeeRecipient solana . PublicKey
FeeBasisPoints uint64
Fee uint64
Creator solana . PublicKey
2025-11-24 17:47:56 +08:00
CreatorFeeBasisPoints uint64
CreatorFee uint64
2026-02-26 16:11:34 +08:00
TrackVolume bool
TotalUnclaimedTokens uint64
TotalClaimedTokens uint64
CurrentSolVolume uint64
LastUpdateTimestamp int64
IxName string
MayhemMode bool
CashbackFeeBasisPoints uint64
Cashback uint64
2026-05-08 11:21:30 +08:00
BuybackFeeBasisPoints uint64
BuybackFee uint64
Shareholders [ ] PumpShareholder
QuoteMint solana . PublicKey
QuoteAmount uint64
VirtualQuoteReserves uint64
RealQuoteReserves uint64
}
type PumpShareholder struct {
Address solana . PublicKey
ShareBps uint16
}
type pumpTradeEventLegacy struct {
Mint solana . PublicKey
SolAmount uint64
TokenAmount uint64
IsBuy bool
User solana . PublicKey
Timestamp int64
VirtualSolReserves uint64
VirtualTokenReserves uint64
RealSolReserves uint64
RealTokenReserves uint64
FeeRecipient solana . PublicKey
FeeBasisPoints uint64
Fee uint64
Creator solana . PublicKey
CreatorFeeBasisPoints uint64
CreatorFee uint64
TrackVolume bool
TotalUnclaimedTokens uint64
TotalClaimedTokens uint64
CurrentSolVolume uint64
LastUpdateTimestamp int64
IxName string
MayhemMode bool
CashbackFeeBasisPoints uint64
Cashback uint64
}
type pumpTradeEventLegacyV0 struct {
Mint solana . PublicKey
SolAmount uint64
TokenAmount uint64
IsBuy bool
User solana . PublicKey
Timestamp int64
VirtualSolReserves uint64
VirtualTokenReserves uint64
RealSolReserves uint64
RealTokenReserves uint64
FeeRecipient solana . PublicKey
FeeBasisPoints uint64
Fee uint64
Creator solana . PublicKey
CreatorFeeBasisPoints uint64
CreatorFee uint64
TrackVolume bool
TotalUnclaimedTokens uint64
TotalClaimedTokens uint64
CurrentSolVolume uint64
LastUpdateTimestamp int64
IxName string
}
func decodePumpTradeEvent ( data [ ] byte ) ( PumpTradeEvent , error ) {
var event PumpTradeEvent
if err := agbinary . NewBorshDecoder ( data ) . Decode ( & event ) ; err == nil {
return event , nil
}
var legacy pumpTradeEventLegacy
if err := agbinary . NewBorshDecoder ( data ) . Decode ( & legacy ) ; err == nil {
return PumpTradeEvent {
Mint : legacy . Mint ,
SolAmount : legacy . SolAmount ,
TokenAmount : legacy . TokenAmount ,
IsBuy : legacy . IsBuy ,
User : legacy . User ,
Timestamp : legacy . Timestamp ,
VirtualSolReserves : legacy . VirtualSolReserves ,
VirtualTokenReserves : legacy . VirtualTokenReserves ,
RealSolReserves : legacy . RealSolReserves ,
RealTokenReserves : legacy . RealTokenReserves ,
FeeRecipient : legacy . FeeRecipient ,
FeeBasisPoints : legacy . FeeBasisPoints ,
Fee : legacy . Fee ,
Creator : legacy . Creator ,
CreatorFeeBasisPoints : legacy . CreatorFeeBasisPoints ,
CreatorFee : legacy . CreatorFee ,
TrackVolume : legacy . TrackVolume ,
TotalUnclaimedTokens : legacy . TotalUnclaimedTokens ,
TotalClaimedTokens : legacy . TotalClaimedTokens ,
CurrentSolVolume : legacy . CurrentSolVolume ,
LastUpdateTimestamp : legacy . LastUpdateTimestamp ,
IxName : legacy . IxName ,
MayhemMode : legacy . MayhemMode ,
CashbackFeeBasisPoints : legacy . CashbackFeeBasisPoints ,
Cashback : legacy . Cashback ,
} , nil
}
var legacyV0 pumpTradeEventLegacyV0
if err := agbinary . NewBorshDecoder ( data ) . Decode ( & legacyV0 ) ; err != nil {
return PumpTradeEvent { } , err
}
return PumpTradeEvent {
Mint : legacyV0 . Mint ,
SolAmount : legacyV0 . SolAmount ,
TokenAmount : legacyV0 . TokenAmount ,
IsBuy : legacyV0 . IsBuy ,
User : legacyV0 . User ,
Timestamp : legacyV0 . Timestamp ,
VirtualSolReserves : legacyV0 . VirtualSolReserves ,
VirtualTokenReserves : legacyV0 . VirtualTokenReserves ,
RealSolReserves : legacyV0 . RealSolReserves ,
RealTokenReserves : legacyV0 . RealTokenReserves ,
FeeRecipient : legacyV0 . FeeRecipient ,
FeeBasisPoints : legacyV0 . FeeBasisPoints ,
Fee : legacyV0 . Fee ,
Creator : legacyV0 . Creator ,
CreatorFeeBasisPoints : legacyV0 . CreatorFeeBasisPoints ,
CreatorFee : legacyV0 . CreatorFee ,
TrackVolume : legacyV0 . TrackVolume ,
TotalUnclaimedTokens : legacyV0 . TotalUnclaimedTokens ,
TotalClaimedTokens : legacyV0 . TotalClaimedTokens ,
CurrentSolVolume : legacyV0 . CurrentSolVolume ,
LastUpdateTimestamp : legacyV0 . LastUpdateTimestamp ,
IxName : legacyV0 . IxName ,
} , nil
2025-11-24 17:47:56 +08:00
}
type PumpTradeFeeArg struct {
IsPump bool
MarketCap [ 16 ] byte
TradeSize uint64
2025-11-20 17:56:45 +08:00
}
type CompleteEvent struct {
User solana . PublicKey
Mint solana . PublicKey
BondingCurve solana . PublicKey
Timestamp int64
}
2026-02-26 16:11:34 +08:00
type PumpTradeArgs struct {
Discriminator [ 8 ] byte
Amount1 uint64
Amount2 uint64
}
2026-04-16 14:24:14 +08:00
func pumpTradeAmountInfoFromArgs ( args PumpTradeArgs ) ( swapMode SwapMode , fixedAmount decimal . Decimal , limitAmount decimal . Decimal , ok bool ) {
switch {
2026-05-08 11:21:30 +08:00
case bytes . Equal ( args . Discriminator [ : ] , pumpBuyExactSolInDiscriminator [ : ] ) ,
bytes . Equal ( args . Discriminator [ : ] , pumpBuyExactQuoteInV2Discriminator [ : ] ) :
2026-04-16 14:24:14 +08:00
return SwapModeExactIn , decimal . NewFromUint64 ( args . Amount1 ) , decimal . NewFromUint64 ( args . Amount2 ) , true
2026-05-08 11:21:30 +08:00
case bytes . Equal ( args . Discriminator [ : ] , pumpBuyDiscriminator [ : ] ) ,
bytes . Equal ( args . Discriminator [ : ] , pumpBuyV2Discriminator [ : ] ) :
2026-04-16 14:24:14 +08:00
return SwapModeExactOut , decimal . NewFromUint64 ( args . Amount1 ) , decimal . NewFromUint64 ( args . Amount2 ) , true
2026-05-08 11:21:30 +08:00
case bytes . Equal ( args . Discriminator [ : ] , pumpSellDiscriminator [ : ] ) ,
bytes . Equal ( args . Discriminator [ : ] , pumpSellV2Discriminator [ : ] ) :
2026-04-16 14:24:14 +08:00
return SwapModeExactIn , decimal . NewFromUint64 ( args . Amount1 ) , decimal . NewFromUint64 ( args . Amount2 ) , true
default :
return SwapModeUnknown , decimal . Zero , decimal . Zero , false
}
}
2026-05-08 11:21:30 +08:00
type pumpTradeAccountLayout struct {
IsV2 bool
FeeRecipient int
BaseMint int
QuoteMint int
BaseTokenProgram int
QuoteTokenProgram int
Pool int
BasePoolToken int
QuotePoolToken int
User int
BaseUserToken int
QuoteUserToken int
}
func pumpTradeLayout ( instr Instruction ) ( pumpTradeAccountLayout , bool ) {
if len ( instr . Data ) < 8 {
return pumpTradeAccountLayout { } , false
}
discriminator := instr . Data [ : 8 ]
switch {
case bytes . Equal ( discriminator , pumpBuyDiscriminator [ : ] ) , bytes . Equal ( discriminator , pumpBuyExactSolInDiscriminator [ : ] ) :
if len ( instr . Accounts ) <= 8 {
return pumpTradeAccountLayout { } , false
}
return pumpTradeAccountLayout {
FeeRecipient : 1 ,
BaseMint : 2 ,
QuoteMint : - 1 ,
BaseTokenProgram : 8 ,
QuoteTokenProgram : - 1 ,
Pool : 3 ,
BasePoolToken : 4 ,
QuotePoolToken : - 1 ,
User : 6 ,
BaseUserToken : 5 ,
QuoteUserToken : - 1 ,
} , true
case bytes . Equal ( discriminator , pumpSellDiscriminator [ : ] ) :
if len ( instr . Accounts ) <= 9 {
return pumpTradeAccountLayout { } , false
}
return pumpTradeAccountLayout {
FeeRecipient : 1 ,
BaseMint : 2 ,
QuoteMint : - 1 ,
BaseTokenProgram : 9 ,
QuoteTokenProgram : - 1 ,
Pool : 3 ,
BasePoolToken : 4 ,
QuotePoolToken : - 1 ,
User : 6 ,
BaseUserToken : 5 ,
QuoteUserToken : - 1 ,
} , true
case bytes . Equal ( discriminator , pumpBuyV2Discriminator [ : ] ) ,
bytes . Equal ( discriminator , pumpBuyExactQuoteInV2Discriminator [ : ] ) ,
bytes . Equal ( discriminator , pumpSellV2Discriminator [ : ] ) :
if len ( instr . Accounts ) <= 15 {
return pumpTradeAccountLayout { } , false
}
return pumpTradeAccountLayout {
IsV2 : true ,
FeeRecipient : 6 ,
BaseMint : 1 ,
QuoteMint : 2 ,
BaseTokenProgram : 3 ,
QuoteTokenProgram : 4 ,
Pool : 10 ,
BasePoolToken : 11 ,
QuotePoolToken : 12 ,
User : 13 ,
BaseUserToken : 14 ,
QuoteUserToken : 15 ,
} , true
default :
return pumpTradeAccountLayout { } , false
}
}
func pumpInstructionIsSell ( data [ ] byte ) bool {
return len ( data ) >= 8 && ( bytes . Equal ( data [ : 8 ] , pumpSellDiscriminator [ : ] ) || bytes . Equal ( data [ : 8 ] , pumpSellV2Discriminator [ : ] ) )
}
func pumpInstructionIsExactQuoteIn ( data [ ] byte ) bool {
return len ( data ) >= 8 && ( bytes . Equal ( data [ : 8 ] , pumpBuyExactSolInDiscriminator [ : ] ) || bytes . Equal ( data [ : 8 ] , pumpBuyExactQuoteInV2Discriminator [ : ] ) )
}
func pumpAccount ( result * RawTx , instr Instruction , accountIndex int ) solana . PublicKey {
if accountIndex < 0 || accountIndex >= len ( instr . Accounts ) {
return solana . PublicKey { }
}
listIndex := instr . Accounts [ accountIndex ]
if listIndex < 0 || listIndex >= len ( result . accountList ) {
return solana . PublicKey { }
}
return result . accountList [ listIndex ]
}
func pumpCreateQuoteAccounts ( result * RawTx , instr Instruction , createEvent PumpCreateEvent ) ( solana . PublicKey , solana . PublicKey , uint8 ) {
quoteMint := createEvent . QuoteMint
quoteTokenProgram := solana . PublicKey { }
optionalStart := - 1
if len ( instr . Data ) >= 8 && bytes . Equal ( instr . Data [ : 8 ] , pumpCreateV2Discriminator [ : ] ) {
optionalStart = 16
}
if optionalStart >= 0 && len ( instr . Accounts ) > optionalStart {
accountQuoteMint := pumpAccount ( result , instr , optionalStart )
if quoteMint . IsZero ( ) && ! accountQuoteMint . IsZero ( ) && ! accountQuoteMint . Equals ( wSolMint ) {
quoteMint = accountQuoteMint
}
if len ( instr . Accounts ) > optionalStart + 2 && ! quoteMint . IsZero ( ) {
quoteTokenProgram = pumpAccount ( result , instr , optionalStart + 2 )
}
}
if quoteMint . Equals ( wSolMint ) {
quoteTokenProgram = solana . TokenProgramID
}
if quoteTokenProgram . IsZero ( ) && ! quoteMint . IsZero ( ) {
quoteTokenProgram = solana . TokenProgramID
}
return quoteMint , quoteTokenProgram , pumpQuoteDecimals ( result , quoteMint )
}
func pumpMintDecimalsFromBalances ( result * RawTx , mint solana . PublicKey , fallback uint8 ) uint8 {
if mint . IsZero ( ) {
return fallback
}
for _ , balance := range result . Meta . PostTokenBalances {
balance . ParseAccount ( )
if balance . MintAccount . Equals ( mint ) {
return uint8 ( balance . UITokenAmount . Decimals )
}
}
for _ , balance := range result . Meta . PreTokenBalances {
balance . ParseAccount ( )
if balance . MintAccount . Equals ( mint ) {
return uint8 ( balance . UITokenAmount . Decimals )
}
}
return fallback
}
func pumpQuoteDecimals ( result * RawTx , quoteMint solana . PublicKey ) uint8 {
fallback := uint8 ( 9 )
if quoteMint . Equals ( usdcMint ) || quoteMint . Equals ( usd1Mint ) {
fallback = 6
}
return pumpMintDecimalsFromBalances ( result , quoteMint , fallback )
}
func pumpQuoteAmount ( tradeEvent PumpTradeEvent ) uint64 {
if tradeEvent . QuoteAmount != 0 {
return tradeEvent . QuoteAmount
}
return tradeEvent . SolAmount
}
func pumpQuoteReserve ( tradeEvent PumpTradeEvent ) uint64 {
if tradeEvent . RealQuoteReserves != 0 {
return tradeEvent . RealQuoteReserves
}
return tradeEvent . RealSolReserves
}
2026-04-20 16:26:55 +08:00
func pumpCompleteMatchesTradeEvent ( completeEvent CompleteEvent , tradeEvent PumpTradeEvent , bondingCurve solana . PublicKey ) bool {
if completeEvent . Mint != tradeEvent . Mint {
return false
}
if completeEvent . User != tradeEvent . User {
return false
}
if completeEvent . BondingCurve != bondingCurve {
return false
}
return true
}
2026-04-16 14:24:14 +08:00
func normalizePumpQuoteSideMint ( s * Swap ) {
if s . FixedAmountSide == SwapAmountSideQuote && s . FixedMint . IsZero ( ) {
s . FixedMint = wSolMint
}
if s . LimitAmountSide == SwapAmountSideQuote && s . LimitMint . IsZero ( ) {
s . LimitMint = wSolMint
}
if s . ActualLimitAmountSide == SwapAmountSideQuote && s . LimitMint . IsZero ( ) {
s . LimitMint = wSolMint
}
}
2026-02-26 16:11:34 +08:00
func failedTxBuyOrSellParser ( tx * Tx , instruction Instruction , innerInstructions InnerInstructions , offset [ 2 ] uint ) ( [ ] Swap , [ 2 ] uint , error ) {
if tx . Err == nil || tx . Err . UnKnown != "" {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "tx pump failed but error is nil, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
if tx . Err . Variant != InstructionError {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "failed tx pump failed but error variant is not instruction error, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
2026-03-02 15:47:11 +08:00
if tx . Err . Enum != Custom && tx . Err . Enum != ComputationalBudgetExceeded && tx . Err . Enum != ProgramFailedToComplete {
2026-02-26 16:11:34 +08:00
return nil , increaseOffset ( offset ) , fmt . Errorf ( "failed tx pump failed but error is not custom or computational budget exceeded, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
if tx . Err . Enum == Custom {
if ! ( tx . Err . CustomCode == 1 ||
tx . Err . CustomCode == 6042 ||
tx . Err . CustomCode == 6041 ||
tx . Err . CustomCode == 6040 ||
tx . Err . CustomCode == 6023 || tx . Err . CustomCode == 6021 || tx . Err . CustomCode == 6003 || tx . Err . CustomCode == 6002 ) {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "failed tx pump failed but custom error code is unexpected, offset, %d, %d, code: %d" , offset [ 0 ] , offset [ 1 ] , tx . Err . CustomCode )
}
}
result := tx . rawTx
var entryContract = result . accountList [ result . Transaction . Message . Instructions [ offset [ 0 ] ] . ProgramIDIndex ]
2026-05-08 11:21:30 +08:00
layout , ok := pumpTradeLayout ( instruction )
if ! ok {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "unknown pump trade instruction account layout, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
user := pumpAccount ( result , instruction , layout . User )
ataUserIdx := instruction . Accounts [ layout . BaseUserToken ]
userIndex := instruction . Accounts [ layout . User ]
mint := pumpAccount ( result , instruction , layout . BaseMint )
quoteMint := pumpAccount ( result , instruction , layout . QuoteMint )
quoteTokenProgram := pumpAccount ( result , instruction , layout . QuoteTokenProgram )
2026-02-26 16:11:34 +08:00
var args PumpTradeArgs
err := agbinary . NewBorshDecoder ( instruction . Data [ : ] ) . Decode ( & args )
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "failed tx pump buy/sell decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
var event string
var (
2026-05-08 11:21:30 +08:00
quoteAmount , tokenAmount uint64
2026-02-26 16:11:34 +08:00
)
2026-05-08 11:21:30 +08:00
if bytes . Equal ( args . Discriminator [ : ] , pumpBuyExactSolInDiscriminator [ : ] ) ||
bytes . Equal ( args . Discriminator [ : ] , pumpBuyExactQuoteInV2Discriminator [ : ] ) {
2026-02-26 16:11:34 +08:00
event = "buy_failed"
2026-05-08 11:21:30 +08:00
quoteAmount = args . Amount1
2026-02-26 16:11:34 +08:00
tokenAmount = args . Amount2
2026-05-08 11:21:30 +08:00
} else if bytes . Equal ( args . Discriminator [ : ] , pumpBuyDiscriminator [ : ] ) ||
bytes . Equal ( args . Discriminator [ : ] , pumpBuyV2Discriminator [ : ] ) {
2026-02-26 16:11:34 +08:00
event = "buy_failed"
2026-05-08 11:21:30 +08:00
quoteAmount = args . Amount2
2026-02-26 16:11:34 +08:00
tokenAmount = args . Amount1
2026-05-08 11:21:30 +08:00
} else if bytes . Equal ( args . Discriminator [ : ] , pumpSellDiscriminator [ : ] ) ||
bytes . Equal ( args . Discriminator [ : ] , pumpSellV2Discriminator [ : ] ) {
2026-02-26 16:11:34 +08:00
event = "sell_failed"
2026-05-08 11:21:30 +08:00
quoteAmount = args . Amount2
2026-02-26 16:11:34 +08:00
tokenAmount = args . Amount1
} else {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "unknown pump trade instruction discriminator, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
2026-05-08 11:21:30 +08:00
baseTokenProgram := pumpAccount ( result , instruction , layout . BaseTokenProgram )
2026-02-26 16:11:34 +08:00
if ! user . IsOnCurve ( ) && ( entryContract . Equals ( okxDexRoutersV2 ) || entryContract . Equals ( okxAggregatorV2 ) ) {
userBaseAmount , ataIndex := tokenBalanceChange ( result , 0 , baseTokenProgram , mint )
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
if ! userBaseAmount . IsZero ( ) {
user = result . accountList [ 0 ]
userIndex = 0
ataUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx ( result , ataUserIdx )
2026-05-08 11:21:30 +08:00
userQuote := decimal . Zero
if layout . IsV2 && ! quoteMint . Equals ( wSolMint ) {
userQuote = getAccountBalanceAfterTx ( result , instruction . Accounts [ layout . QuoteUserToken ] )
} else {
userQuoteLamports , _ := GetSolAfterTx ( result , userIndex )
userQuote = decimal . NewFromUint64 ( userQuoteLamports )
}
2026-02-26 16:11:34 +08:00
2026-05-08 11:21:30 +08:00
bcIdx := instruction . Accounts [ layout . Pool ]
bcAtaIndex := instruction . Accounts [ layout . BasePoolToken ]
quoteReserves := decimal . Zero
if layout . IsV2 && ! quoteMint . Equals ( wSolMint ) {
quoteReserves = getAccountBalanceAfterTx ( result , instruction . Accounts [ layout . QuotePoolToken ] )
} else {
solReserves , _ := GetSolAfterTx ( result , bcIdx )
quoteReserves = decimal . NewFromUint64 ( solReserves )
}
2026-02-26 16:11:34 +08:00
tokenReserves := getAccountBalanceAfterTx ( result , bcAtaIndex )
swaps := [ ] Swap {
{
Program : SolProgramPump ,
Event : event ,
2026-05-08 11:21:30 +08:00
Pool : pumpAccount ( result , instruction , layout . Pool ) ,
2026-02-26 16:11:34 +08:00
BaseMint : mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
2026-02-26 16:11:34 +08:00
BaseTokenProgram : baseTokenProgram ,
2026-05-08 11:21:30 +08:00
QuoteTokenProgram : quoteTokenProgram ,
2026-02-26 16:11:34 +08:00
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : pumpQuoteDecimals ( result , quoteMint ) ,
2026-02-26 16:11:34 +08:00
User : user ,
BaseAmount : decimal . NewFromUint64 ( tokenAmount ) ,
2026-05-08 11:21:30 +08:00
QuoteAmount : decimal . NewFromUint64 ( quoteAmount ) ,
2026-02-26 16:11:34 +08:00
BaseReserve : tokenReserves ,
2026-05-08 11:21:30 +08:00
QuoteReserve : quoteReserves ,
Mayhem : isMayhemPump ( pumpAccount ( result , instruction , layout . FeeRecipient ) ) ,
2026-02-26 16:11:34 +08:00
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuote ,
2026-02-26 16:11:34 +08:00
EntryContract : entryContract ,
} ,
}
2026-04-16 14:24:14 +08:00
if swapMode , fixedAmount , limitAmount , ok := pumpTradeAmountInfoFromArgs ( args ) ; ok {
swaps [ 0 ] . SetSwapAmountInfo ( swapMode , fixedAmount , limitAmount )
normalizePumpQuoteSideMint ( & swaps [ 0 ] )
}
2026-02-26 16:11:34 +08:00
return swaps , offset , nil
}
2025-11-21 12:01:44 +08:00
func BuyOrSellParser ( tx * Tx , instruction Instruction , innerInstructions InnerInstructions , offset [ 2 ] uint ) ( [ ] Swap , [ 2 ] uint , error ) {
result := tx . rawTx
2025-11-20 17:56:45 +08:00
var entryContract = result . accountList [ result . Transaction . Message . Instructions [ offset [ 0 ] ] . ProgramIDIndex ]
var err error
var programIndex = instruction . ProgramIDIndex
2026-05-08 11:21:30 +08:00
layout , ok := pumpTradeLayout ( instruction )
if ! ok {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "unknown pump trade instruction account layout, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
2025-11-20 17:56:45 +08:00
2025-11-24 17:47:56 +08:00
feeEventProgramIndex := 0
for i , b := range result . accountList {
if b . Equals ( pumpFeesProgram ) {
feeEventProgramIndex = i
break
}
}
2025-11-20 17:56:45 +08:00
var (
tradeEvent PumpTradeEvent
2025-11-24 17:47:56 +08:00
tradeFeeArg PumpTradeFeeArg
2025-11-20 17:56:45 +08:00
completeEvent CompleteEvent
completed bool
newoffset [ 2 ] uint
2026-04-20 16:26:55 +08:00
tradeFound bool
2025-11-20 17:56:45 +08:00
)
var prefixLen = offset [ 1 ]
inners , err := getInnerInstructions ( innerInstructions , prefixLen )
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump create get inner instructions error: %v,offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
2026-03-02 20:04:38 +08:00
if ! entryContract . Equals ( axiomOuterContract ) {
if instruction . StackHeight != nil && * instruction . StackHeight > 2 {
for _ , innerInstr := range innerInstructions . Instructions {
if innerInstr . StackHeight != nil && * innerInstr . StackHeight == * instruction . StackHeight - 1 {
entryContract = result . accountList [ innerInstr . ProgramIDIndex ]
break
}
2025-12-22 17:56:40 +08:00
}
}
}
2025-11-20 17:56:45 +08:00
for innerIndex , innerInstr := range inners {
2025-11-24 17:47:56 +08:00
if innerInstr . ProgramIDIndex == feeEventProgramIndex && bytes . Equal ( innerInstr . Data [ : 8 ] , pumpGetFeesDiscriminator [ : ] ) {
2026-05-13 16:53:22 +08:00
if tradeFound {
continue
}
2025-11-24 17:47:56 +08:00
err = agbinary . NewBorshDecoder ( innerInstr . Data [ 8 : ] ) . Decode ( & tradeFeeArg )
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump get fees event decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
continue
}
2025-11-20 17:56:45 +08:00
if innerInstr . ProgramIDIndex == programIndex && bytes . Equal ( innerInstr . Data [ : 8 ] , pumpEventDiscriminator [ : ] ) {
if bytes . Equal ( innerInstr . Data [ 8 : 16 ] , pumpTradeEventDiscriminator [ 8 : 16 ] ) {
2026-04-20 16:26:55 +08:00
if tradeFound {
break
}
2026-05-08 11:21:30 +08:00
tradeEvent , err = decodePumpTradeEvent ( innerInstr . Data [ 16 : ] )
2025-11-20 17:56:45 +08:00
if offset [ 1 ] == 0 {
newoffset = [ 2 ] uint { offset [ 0 ] + 1 , offset [ 1 ] }
} else {
newoffset = [ 2 ] uint { offset [ 0 ] , prefixLen + uint ( innerIndex ) + 1 }
}
if err != nil {
return nil , newoffset , fmt . Errorf ( "pump buy event decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
2026-05-08 11:21:30 +08:00
expectedIsBuy := ! pumpInstructionIsSell ( instruction . Data )
2026-04-20 16:26:55 +08:00
if tradeEvent . IsBuy != expectedIsBuy {
tradeEvent = PumpTradeEvent { }
continue
}
tradeFound = true
2025-11-20 17:56:45 +08:00
if ! tradeEvent . IsBuy {
break
}
} else if bytes . Equal ( innerInstr . Data [ 8 : 16 ] , pumpCompleteEventDiscriminator [ : ] ) {
2026-04-20 16:26:55 +08:00
if ! tradeFound {
continue
}
2025-11-20 17:56:45 +08:00
err = agbinary . NewBorshDecoder ( innerInstr . Data [ 16 : ] ) . Decode ( & completeEvent )
2026-04-20 16:26:55 +08:00
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump completeEvent event decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
2026-05-08 11:21:30 +08:00
if ! pumpCompleteMatchesTradeEvent ( completeEvent , tradeEvent , pumpAccount ( result , instruction , layout . Pool ) ) {
2026-04-20 16:26:55 +08:00
break
}
2025-11-20 17:56:45 +08:00
if offset [ 1 ] == 0 {
newoffset = [ 2 ] uint { offset [ 0 ] + 1 , offset [ 1 ] }
} else {
newoffset = [ 2 ] uint { offset [ 0 ] , prefixLen + uint ( innerIndex ) + 1 }
}
completed = true
break
}
}
}
2026-05-08 11:21:30 +08:00
if ! tradeFound {
2025-11-20 17:56:45 +08:00
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pmp buy/sell event not found, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
offset = [ 2 ] uint { newoffset [ 0 ] , newoffset [ 1 ] }
2026-04-20 16:26:55 +08:00
var args PumpTradeArgs
if err := agbinary . NewBorshDecoder ( instruction . Data [ : ] ) . Decode ( & args ) ; err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "failed tx pump buy/sell decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
2025-11-20 17:56:45 +08:00
event := ""
2026-05-08 11:21:30 +08:00
baseTokenProgram := pumpAccount ( result , instruction , layout . BaseTokenProgram )
quoteMint := tradeEvent . QuoteMint
if quoteMint . IsZero ( ) {
quoteMint = pumpAccount ( result , instruction , layout . QuoteMint )
}
quoteTokenProgram := pumpAccount ( result , instruction , layout . QuoteTokenProgram )
2025-11-20 17:56:45 +08:00
if tradeEvent . IsBuy {
event = "buy"
} else {
event = "sell"
}
2025-11-21 12:01:44 +08:00
if _ , exists := tx . Token [ tradeEvent . Mint ] ; ! exists {
tx . Token [ tradeEvent . Mint ] = TokenMeta {
Mint : tradeEvent . Mint ,
TokenProgram : baseTokenProgram ,
Decimals : 6 ,
}
}
2025-12-22 17:56:40 +08:00
var user = tradeEvent . User
2026-05-08 11:21:30 +08:00
ataUserIdx := instruction . Accounts [ layout . BaseUserToken ]
userIndex := instruction . Accounts [ layout . User ]
2025-12-22 17:56:40 +08:00
if ! tradeEvent . User . IsOnCurve ( ) && ( entryContract . Equals ( okxDexRoutersV2 ) || entryContract . Equals ( okxAggregatorV2 ) ) {
userBaseAmount , ataIndex := tokenBalanceChange ( result , 0 , baseTokenProgram , tradeEvent . Mint )
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
if ! userBaseAmount . IsZero ( ) {
user = result . accountList [ 0 ]
userIndex = 0
ataUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx ( result , ataUserIdx )
2026-05-08 11:21:30 +08:00
userQuote := decimal . Zero
if layout . IsV2 && ! quoteMint . Equals ( wSolMint ) {
userQuote = getAccountBalanceAfterTx ( result , instruction . Accounts [ layout . QuoteUserToken ] )
} else {
userQuoteLamports , _ := GetSolAfterTx ( result , userIndex )
userQuote = decimal . NewFromUint64 ( userQuoteLamports )
}
2025-12-22 17:56:40 +08:00
2026-05-08 11:21:30 +08:00
quoteAmount := pumpQuoteAmount ( tradeEvent )
if tradeEvent . IsBuy && pumpInstructionIsExactQuoteIn ( instruction . Data ) && ! layout . IsV2 {
2025-11-24 17:47:56 +08:00
fee := tradeEvent . Fee + tradeEvent . CreatorFee
2026-05-08 11:21:30 +08:00
quoteAmount = tradeFeeArg . TradeSize
if quoteAmount > fee {
quoteAmount = quoteAmount - fee
2025-11-24 17:47:56 +08:00
}
}
2026-02-26 16:11:34 +08:00
isCashbackCoin := tradeEvent . CashbackFeeBasisPoints > 0 || tradeEvent . Cashback > 0
2025-11-20 17:56:45 +08:00
swaps := [ ] Swap {
{
Program : SolProgramPump ,
Event : event ,
2026-05-08 11:21:30 +08:00
Pool : pumpAccount ( result , instruction , layout . Pool ) ,
2025-11-20 17:56:45 +08:00
BaseMint : tradeEvent . Mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
2025-11-20 17:56:45 +08:00
BaseTokenProgram : baseTokenProgram ,
2026-05-08 11:21:30 +08:00
QuoteTokenProgram : quoteTokenProgram ,
2025-11-20 17:56:45 +08:00
Creator : tradeEvent . Creator ,
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : pumpQuoteDecimals ( result , quoteMint ) ,
2025-12-22 17:56:40 +08:00
User : user ,
2025-11-20 17:56:45 +08:00
BaseAmount : decimal . NewFromUint64 ( tradeEvent . TokenAmount ) ,
2026-05-08 11:21:30 +08:00
QuoteAmount : decimal . NewFromUint64 ( quoteAmount ) ,
2025-11-20 17:56:45 +08:00
BaseReserve : decimal . NewFromUint64 ( tradeEvent . RealTokenReserves ) ,
2026-05-08 11:21:30 +08:00
QuoteReserve : decimal . NewFromUint64 ( pumpQuoteReserve ( tradeEvent ) ) ,
Mayhem : tradeEvent . MayhemMode || isMayhemPump ( pumpAccount ( result , instruction , layout . FeeRecipient ) ) ,
2025-11-20 17:56:45 +08:00
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuote ,
2025-11-20 17:56:45 +08:00
EntryContract : entryContract ,
2026-02-26 16:11:34 +08:00
Cashback : isCashbackCoin ,
2025-11-20 17:56:45 +08:00
} ,
}
2026-04-20 16:26:55 +08:00
if swapMode , fixedAmount , limitAmount , ok := pumpTradeAmountInfoFromArgs ( args ) ; ok {
swaps [ 0 ] . SetSwapAmountInfo ( swapMode , fixedAmount , limitAmount )
normalizePumpQuoteSideMint ( & swaps [ 0 ] )
2026-04-16 14:24:14 +08:00
}
2025-11-20 17:56:45 +08:00
if completed {
swaps = append ( swaps , Swap {
Program : SolProgramPump ,
Event : "complete" ,
2026-05-08 11:21:30 +08:00
Pool : pumpAccount ( result , instruction , layout . Pool ) ,
2025-11-20 17:56:45 +08:00
BaseMint : tradeEvent . Mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
BaseTokenProgram : baseTokenProgram ,
QuoteTokenProgram : quoteTokenProgram ,
2025-11-20 17:56:45 +08:00
Creator : tradeEvent . Creator ,
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : pumpQuoteDecimals ( result , quoteMint ) ,
2025-12-22 17:56:40 +08:00
User : user ,
2026-02-12 10:37:59 +08:00
BaseReserve : decimal . NewFromUint64 ( tradeEvent . RealTokenReserves ) ,
2026-05-08 11:21:30 +08:00
QuoteReserve : decimal . NewFromUint64 ( pumpQuoteReserve ( tradeEvent ) ) ,
Mayhem : tradeEvent . MayhemMode || isMayhemPump ( pumpAccount ( result , instruction , layout . FeeRecipient ) ) ,
2025-11-20 17:56:45 +08:00
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuote ,
2025-11-20 17:56:45 +08:00
EntryContract : entryContract ,
} )
}
2025-12-22 17:56:40 +08:00
2025-11-20 17:56:45 +08:00
return swaps , offset , nil
}
type MigrateEvent struct {
User solana . PublicKey
Mint solana . PublicKey
//Creator solana.PublicKey
MintAmount uint64
SolAmount uint64
PoolMigrationFee uint64
//CreatorFee uint64
BondingCurve solana . PublicKey
TimeStamp int64
Pool solana . PublicKey
}
2026-05-08 11:21:30 +08:00
type pumpMigrateAccountLayout struct {
IsV2 bool
BaseMint int
QuoteMint int
Pool int
BasePoolToken int
QuotePoolToken int
User int
BaseTokenProgram int
QuoteTokenProgram int
}
func pumpMigrateLayout ( instr Instruction ) ( pumpMigrateAccountLayout , bool ) {
if len ( instr . Data ) < 8 {
return pumpMigrateAccountLayout { } , false
}
discriminator := instr . Data [ : 8 ]
switch {
case bytes . Equal ( discriminator , pumpMigrateDiscriminator [ : ] ) :
if len ( instr . Accounts ) <= 14 {
return pumpMigrateAccountLayout { } , false
}
return pumpMigrateAccountLayout {
BaseMint : 2 ,
QuoteMint : 14 ,
Pool : 3 ,
BasePoolToken : 4 ,
QuotePoolToken : - 1 ,
User : 5 ,
BaseTokenProgram : 7 ,
QuoteTokenProgram : - 1 ,
} , true
case bytes . Equal ( discriminator , pumpMigrateV2Discriminator [ : ] ) :
if len ( instr . Accounts ) <= 20 {
return pumpMigrateAccountLayout { } , false
}
return pumpMigrateAccountLayout {
IsV2 : true ,
BaseMint : 2 ,
QuoteMint : 3 ,
Pool : 4 ,
BasePoolToken : 5 ,
QuotePoolToken : 6 ,
User : 7 ,
BaseTokenProgram : 19 ,
QuoteTokenProgram : 20 ,
} , true
default :
return pumpMigrateAccountLayout { } , false
}
}
func decimalFromUint64WithFallback ( primary , fallback uint64 ) decimal . Decimal {
if primary != 0 {
return decimal . NewFromUint64 ( primary )
}
return decimal . NewFromUint64 ( fallback )
}
2025-11-21 12:01:44 +08:00
func MigrateParser ( tx * Tx , instr Instruction , innerInstructions InnerInstructions , offset [ 2 ] uint ) ( [ ] Swap , [ 2 ] uint , error ) {
result := tx . rawTx
2025-11-20 17:56:45 +08:00
var entryContract = result . accountList [ result . Transaction . Message . Instructions [ offset [ 0 ] ] . ProgramIDIndex ]
var err error
programIndex := instr . ProgramIDIndex
2026-05-08 11:21:30 +08:00
layout , ok := pumpMigrateLayout ( instr )
if ! ok {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "unknown pump migrate instruction account layout, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
2025-11-20 17:56:45 +08:00
ammprogramIdx := 0
for i , b := range result . accountList {
if b . Equals ( pumpAmmProgram ) {
ammprogramIdx = i
break
}
}
if ammprogramIdx == 0 {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump migrate, amm program id not found in account list, offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
var prefixLen = offset [ 1 ]
inners , err := getInnerInstructions ( innerInstructions , prefixLen )
if err != nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump migrate get inner instructions offset, %d, %d" , offset [ 0 ] , offset [ 1 ] )
}
var (
migrateEvent MigrateEvent
createEvent ammCreatePoolEvent
newoffset [ 2 ] uint
)
for innerIndex , innerInstr := range inners {
if innerInstr . ProgramIDIndex == ammprogramIdx && bytes . Equal ( innerInstr . Data [ : 8 ] , pumpAmmEventDiscriminator [ : ] ) {
if bytes . Equal ( innerInstr . Data [ 8 : 16 ] , pumpAmmCreateEventDiscriminator [ : ] ) {
err = agbinary . NewBorshDecoder ( innerInstr . Data [ 16 : ] ) . Decode ( & createEvent )
if offset [ 1 ] == 0 {
newoffset = [ 2 ] uint { offset [ 0 ] + 1 , offset [ 1 ] }
} else {
newoffset = [ 2 ] uint { offset [ 0 ] , prefixLen + uint ( innerIndex ) + 1 }
}
if err != nil {
return nil , newoffset , fmt . Errorf ( "pump amm createEvent decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
}
//
} else if innerInstr . ProgramIDIndex == programIndex && bytes . Equal ( innerInstr . Data [ : 8 ] , pumpEventDiscriminator [ : ] ) {
if bytes . Equal ( innerInstr . Data [ 8 : 16 ] , pumpMigrateEventDiscriminator [ : ] ) {
err = agbinary . NewBorshDecoder ( innerInstr . Data [ 16 : ] ) . Decode ( & migrateEvent )
if offset [ 1 ] == 0 {
newoffset = [ 2 ] uint { offset [ 0 ] + 1 , offset [ 1 ] }
} else {
newoffset = [ 2 ] uint { offset [ 0 ] , prefixLen + uint ( innerIndex ) + 1 }
}
if err != nil {
return nil , newoffset , fmt . Errorf ( "pump buy event decode error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
break
}
}
}
if migrateEvent == ( MigrateEvent { } ) || createEvent == ( ammCreatePoolEvent { } ) {
offset = [ 2 ] uint { newoffset [ 0 ] , newoffset [ 1 ] }
return nil , increaseOffset ( offset ) , InstructionIgnoredError
}
offset = [ 2 ] uint { newoffset [ 0 ] , newoffset [ 1 ] }
// verify migrate by checking create pool and migrate event
2026-05-08 11:21:30 +08:00
userIndex := instr . Accounts [ layout . User ]
ataBondingCurveAccountIndex := instr . Accounts [ layout . BasePoolToken ]
2025-11-20 17:56:45 +08:00
bc , err := getTokenBalanceAfterTx ( result , ataBondingCurveAccountIndex )
if err != nil || bc == nil {
return nil , increaseOffset ( offset ) , fmt . Errorf ( "pump migrate get bonding curve balance error: %v, offset, %d, %d" , err , offset [ 0 ] , offset [ 1 ] )
}
baseTokenProgram := bc . ProgramIDAccount
2026-05-08 11:21:30 +08:00
if layout . IsV2 {
baseTokenProgram = pumpAccount ( result , instr , layout . BaseTokenProgram )
}
quoteMint := createEvent . QuoteMint
if quoteMint . IsZero ( ) {
quoteMint = pumpAccount ( result , instr , layout . QuoteMint )
}
quoteTokenProgram := pumpAccount ( result , instr , layout . QuoteTokenProgram )
if quoteTokenProgram . IsZero ( ) && ! quoteMint . IsZero ( ) {
quoteTokenProgram = solana . TokenProgramID
}
quoteDecimals := createEvent . QuoteMintDecimals
if quoteDecimals == 0 {
quoteDecimals = pumpQuoteDecimals ( result , quoteMint )
}
2025-11-20 17:56:45 +08:00
var userBase decimal . Decimal
if result . accountList [ userIndex ] . Equals ( pumpMigrationAccount ) {
userBase = decimal . Zero
} else {
userBase = GetTokenBalanceAfterTx ( result , userIndex , baseTokenProgram , migrateEvent . Mint )
}
2026-05-08 11:21:30 +08:00
userQuote := decimal . Zero
if layout . IsV2 && ! quoteMint . Equals ( wSolMint ) {
userQuote = GetTokenBalanceAfterTx ( result , userIndex , quoteTokenProgram , quoteMint )
} else {
userQuoteLamports , _ := GetSolAfterTx ( result , userIndex )
userQuote = decimal . NewFromUint64 ( userQuoteLamports )
}
baseAmount := decimalFromUint64WithFallback ( createEvent . BaseAmountIn , migrateEvent . MintAmount )
quoteAmount := decimalFromUint64WithFallback ( createEvent . QuoteAmountIn , migrateEvent . SolAmount )
baseReserve := decimalFromUint64WithFallback ( createEvent . PoolBaseAmount , migrateEvent . MintAmount )
quoteReserve := decimalFromUint64WithFallback ( createEvent . PoolQuoteAmount , migrateEvent . SolAmount )
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
if _ , exists := tx . Token [ migrateEvent . Mint ] ; ! exists {
tx . Token [ migrateEvent . Mint ] = TokenMeta {
Mint : migrateEvent . Mint ,
TokenProgram : baseTokenProgram ,
Decimals : 6 ,
}
}
2025-11-20 17:56:45 +08:00
swaps := [ ] Swap {
{
Program : SolProgramPump ,
Event : "migrate" ,
Pool : migrateEvent . BondingCurve ,
BaseMint : migrateEvent . Mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
2025-11-20 17:56:45 +08:00
BaseTokenProgram : baseTokenProgram ,
2026-05-08 11:21:30 +08:00
QuoteTokenProgram : quoteTokenProgram ,
2025-11-20 17:56:45 +08:00
Creator : createEvent . Creator ,
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : quoteDecimals ,
2025-11-20 17:56:45 +08:00
User : migrateEvent . User ,
//BaseAmount: decimal.Decimal{},
//QuoteAmount: decimal.Decimal{},
2026-05-08 11:21:30 +08:00
BaseReserve : baseReserve ,
QuoteReserve : quoteReserve ,
2026-02-09 14:46:19 +08:00
Mayhem : createEvent . IsMayhemMode ,
MigrateTopProgram : pumpAmmProgram ,
MigrateToPool : migrateEvent . Pool ,
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuote ,
2026-02-09 14:46:19 +08:00
EntryContract : entryContract ,
2025-11-20 17:56:45 +08:00
} ,
}
swaps = append ( swaps , Swap {
Program : SolProgramPumpAMM ,
Event : "create" ,
Pool : migrateEvent . Pool ,
BaseMint : migrateEvent . Mint ,
2026-05-08 11:21:30 +08:00
QuoteMint : quoteMint ,
2025-11-20 17:56:45 +08:00
BaseTokenProgram : baseTokenProgram ,
2026-05-08 11:21:30 +08:00
QuoteTokenProgram : quoteTokenProgram ,
2025-11-20 17:56:45 +08:00
Creator : createEvent . Creator ,
BaseMintDecimals : 6 ,
2026-05-08 11:21:30 +08:00
QuoteMintDecimals : quoteDecimals ,
2025-11-20 17:56:45 +08:00
User : migrateEvent . User ,
2026-05-08 11:21:30 +08:00
BaseAmount : baseAmount ,
QuoteAmount : quoteAmount ,
BaseReserve : baseReserve ,
QuoteReserve : quoteReserve ,
2025-11-20 17:56:45 +08:00
Mayhem : createEvent . IsMayhemMode ,
UserBaseBalance : userBase ,
2026-05-08 11:21:30 +08:00
UserQuoteBalance : userQuote ,
2025-11-20 17:56:45 +08:00
EntryContract : entryContract ,
} )
return swaps , offset , nil
}