Files
pump-parser/pump.go

1193 lines
40 KiB
Go
Raw Normal View History

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])
}
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
}