Files
pump-parser/pumpamm.go

942 lines
35 KiB
Go
Raw Permalink 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"
)
type ammBuyEvent struct {
TimeStamp int64
BaseAmountOut uint64
MaxQuoteAmountIn uint64
UserBaseTokenReserve uint64
UserQuoteTokenReserve uint64
PoolBaseTokenReserve uint64
PoolQuoteTokenReserve uint64
QuoteAmountIn uint64
LpFeeBasisPoints uint64
LpFee uint64
2025-12-02 15:51:53 +08:00
ProtocolFeeBasisPoints uint64
2025-11-20 17:56:45 +08:00
ProtocolFee uint64
QuoteAmountInWithLpFee uint64
UserQuoteAmountIn uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
ProtocolFeeRecipient solana.PublicKey
ProtocolFeeRecipientTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
2025-12-02 15:51:53 +08:00
CoinCreatorFeeBasisPoints uint64
CoinCreatorFee uint64
TrackVolume bool
TotalUnclaimedTokens uint64
TotalClaimedTokens uint64
CurrentSolVolume uint64
LastUpdateTimestamp int64
MinBaseAmountOut uint64
IxName string
2026-02-27 02:07:52 +08:00
CashbackFeeBasisPoints uint64
Cashback uint64
2025-11-20 17:56:45 +08:00
}
type ammCreatePoolEvent struct {
TimeStamp int64
Index uint16
Creator solana.PublicKey
BaseMint solana.PublicKey
QuoteMint solana.PublicKey
BaseMintDecimals uint8
QuoteMintDecimals uint8
BaseAmountIn uint64
QuoteAmountIn uint64
PoolBaseAmount uint64
PoolQuoteAmount uint64
MinimumLiquidity uint64
InitialLiquidity uint64
LpTokenAmountOut uint64
PoolBump uint8
Pool solana.PublicKey
LpMint solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
IsMayhemMode bool
}
type ammDepositEvent struct {
TimeStamp int64
LpTokenAmountOut uint64
MaxBaseAmountIn uint64
MaxQuoteAmountIn uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
BaseAmountIn uint64
QuoteAmountIn uint64
LpMintSupply uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
UserPoolTokenAccount solana.PublicKey
}
type ammSellEvent struct {
Timestamp int64
BaseAmountIn uint64
MinQuoteAmountOut uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
QuoteAmountOut uint64
LpFeeBasisPoints uint64
LpFee uint64
ProtocolFeeBasisPoints uint64
ProtocolFee uint64
QuoteAmountOutWithoutLpFee uint64
UserQuoteAmountOut uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
ProtocolFeeRecipient solana.PublicKey
ProtocolFeeRecipientTokenAccount solana.PublicKey
CoinCreator solana.PublicKey
2025-12-02 15:51:53 +08:00
CoinCreatorFeeBasisPoints uint64
CoinCreatorFee uint64
2026-02-27 02:07:52 +08:00
CashbackFeeBasisPoints uint64
Cashback uint64
2025-11-20 17:56:45 +08:00
}
type ammWithdrawEvent struct {
Timestamp int64
LpTokenAmountIn uint64
MinBaseAmountOut uint64
MinQuoteAmountOut uint64
UserBaseTokenReserves uint64
UserQuoteTokenReserves uint64
PoolBaseTokenReserves uint64
PoolQuoteTokenReserves uint64
BaseAmountOut uint64
QuoteAmountOut uint64
LpMintSupply uint64
Pool solana.PublicKey
User solana.PublicKey
UserBaseTokenAccount solana.PublicKey
UserQuoteTokenAccount solana.PublicKey
UserPoolTokenAccount solana.PublicKey
}
2025-11-21 12:01:44 +08:00
func pumpAmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(pumpAmmProgram) {
2025-11-20 17:56:45 +08:00
return nil, increaseOffset(offset), fmt.Errorf("pump amm program instruction not found, offset: %d, %d", offset[0], offset[1])
}
decode := instruction.Data
if len(decode) < 8 {
offset[1] += 1
return nil, increaseOffset(offset), fmt.Errorf("pumpamm program instruction data too short, offset: %d, %d", offset[0], offset[1])
}
discriminator := *(*[8]byte)(decode[:8])
switch discriminator {
case pumpAmmCreateDiscriminator:
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 ammCreatePoolParser(tx, instruction, innerInstructions, offset)
2025-12-23 14:37:12 +08:00
case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator:
2026-02-26 16:11:34 +08:00
if tx.Err != nil {
return failedTxAmmBuyParser(tx, instruction, innerInstructions, offset)
}
2025-11-21 12:01:44 +08:00
return ammBuyParser(tx, instruction, innerInstructions, offset)
2025-11-20 17:56:45 +08:00
case pumpAmmSellDiscriminator:
2026-02-26 16:11:34 +08:00
if tx.Err != nil {
return failedTxAmmSellParser(tx, instruction, innerInstructions, offset)
}
2025-11-21 12:01:44 +08:00
return ammSellParser(tx, instruction, innerInstructions, offset)
2025-11-20 17:56:45 +08:00
case pumpAmmDepositDiscriminator:
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 depositParse(tx, instruction, innerInstructions, offset)
2025-11-20 17:56:45 +08:00
case pumpAmmWithdrawDiscriminator:
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 withdrawParse(tx, instruction, innerInstructions, offset)
2025-11-20 17:56:45 +08:00
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
2025-11-21 12:01:44 +08:00
func ammCreatePoolParser(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 prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var createEvent ammCreatePoolEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmCreateEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&createEvent)
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 amm create pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if createEvent == (ammCreatePoolEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm create pool event not found, offset: %d, %d", offset[0], prefixLen)
}
baseTokenProgram := result.accountList[instruction.Accounts[13]]
quoteTokenProgram := result.accountList[instruction.Accounts[14]]
2025-11-21 12:01:44 +08:00
if _, exists := tx.Token[createEvent.BaseMint]; !exists && !createEvent.BaseMint.Equals(wSolMint) {
tx.Token[createEvent.BaseMint] = TokenMeta{
Mint: createEvent.BaseMint,
Decimals: createEvent.BaseMintDecimals,
TokenProgram: baseTokenProgram,
}
}
if _, exists := tx.Token[createEvent.QuoteMint]; !exists && !createEvent.QuoteMint.Equals(wSolMint) {
tx.Token[createEvent.QuoteMint] = TokenMeta{
Mint: createEvent.QuoteMint,
Decimals: createEvent.QuoteMintDecimals,
TokenProgram: quoteTokenProgram,
}
}
2025-11-20 17:56:45 +08:00
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "create",
Pool: createEvent.Pool,
BaseMint: createEvent.BaseMint,
QuoteMint: createEvent.QuoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: createEvent.CoinCreator,
BaseMintDecimals: createEvent.BaseMintDecimals,
QuoteMintDecimals: createEvent.QuoteMintDecimals,
User: createEvent.Creator,
BaseAmount: decimal.NewFromUint64(createEvent.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(createEvent.QuoteAmountIn),
BaseReserve: decimal.NewFromUint64(createEvent.PoolBaseAmount),
QuoteReserve: decimal.NewFromUint64(createEvent.PoolQuoteAmount),
UserBaseBalance: decimal.Decimal{},
UserQuoteBalance: decimal.Decimal{},
EntryContract: entryContract,
Mayhem: createEvent.IsMayhemMode,
},
}, offset, nil
}
2026-02-26 16:11:34 +08:00
type PumpSwapArgs struct {
Discriminator [8]byte
Amount1 uint64
Amount2 uint64
}
func failedTxAmmBuyParser(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 amm sell 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 amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
}
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell 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 == 6004 ||
tx.Err.CustomCode == 6040 ||
tx.Err.CustomCode == 6039 ||
tx.Err.CustomCode == 6016 ||
tx.Err.CustomCode == 6014) {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell 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]
var args PumpSwapArgs
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var event string
var (
quoteAmount, tokenAmount uint64
)
if bytes.Equal(args.Discriminator[:], pumpAmmBuyV2Discriminator[:]) {
event = "buy_failed"
quoteAmount = args.Amount1
tokenAmount = args.Amount2
} else if bytes.Equal(args.Discriminator[:], pumpAmmBuyDiscriminator[:]) {
event = "buy_failed"
quoteAmount = args.Amount2
tokenAmount = args.Amount1
} else {
return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseTokenProgram,
}
}
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteTokenProgram,
}
}
var eventUser = tx.rawTx.accountList[instruction.Accounts[1]]
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7])
quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8])
return []Swap{
{
Program: SolProgramPumpAMM,
Event: event,
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: eventUser,
BaseAmount: decimal.NewFromUint64(tokenAmount),
QuoteAmount: decimal.NewFromUint64(quoteAmount),
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
}
func failedTxAmmSellParser(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 amm sell 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 amm sell failed but error variant is not instruction error, offset, %d, %d", offset[0], offset[1])
}
if tx.Err.Enum != Custom && tx.Err.Enum != ComputationalBudgetExceeded {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell 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 == 6004 ||
tx.Err.CustomCode == 6040 ||
tx.Err.CustomCode == 6039 ||
tx.Err.CustomCode == 6016 ||
tx.Err.CustomCode == 6014) {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm sell 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]
var args PumpSwapArgs
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed tx pump amm buy failed decode error: %v, offset, %d, %d", err, offset[0], offset[1])
}
var event string
var (
quoteAmount, tokenAmount uint64
)
if bytes.Equal(args.Discriminator[:], pumpAmmSellDiscriminator[:]) {
event = "sell_failed"
tokenAmount = args.Amount1
quoteAmount = args.Amount2
} else {
return nil, increaseOffset(offset), fmt.Errorf("unknown pump amm trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseTokenProgram,
}
}
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteTokenProgram,
}
}
var eventUser = tx.rawTx.accountList[instruction.Accounts[1]]
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
baseReserve := getAccountBalanceAfterTx(result, instruction.Accounts[7])
quoteReserve := getAccountBalanceAfterTx(result, instruction.Accounts[8])
return []Swap{
{
Program: SolProgramPumpAMM,
Event: event,
Pool: tx.rawTx.accountList[instruction.Accounts[0]],
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: eventUser,
BaseAmount: decimal.NewFromUint64(tokenAmount),
QuoteAmount: decimal.NewFromUint64(quoteAmount),
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
}
2025-11-21 12:01:44 +08:00
func ammBuyParser(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 prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
2025-12-22 17:56:40 +08:00
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
2025-12-31 16:53:39 +08:00
for _, innerInstr := range innerInstructions.Instructions {
2025-12-22 17:56:40 +08:00
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
entryContract = result.accountList[innerInstr.ProgramIDIndex]
2026-02-09 16:09:31 +08:00
break
2025-12-22 17:56:40 +08:00
}
}
}
2025-11-20 17:56:45 +08:00
var event ammBuyEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmBuyEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
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 amm buy pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammBuyEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm buy event not found, offset: %d, %d", offset[0], prefixLen)
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
2025-11-21 12:01:44 +08:00
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseTokenProgram,
}
}
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteTokenProgram,
}
}
2025-12-22 17:56:40 +08:00
var eventUser = event.User
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
2026-02-27 02:07:52 +08:00
isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0
2025-11-20 17:56:45 +08:00
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "buy",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
2025-12-22 17:56:40 +08:00
User: eventUser,
2025-11-20 17:56:45 +08:00
BaseAmount: decimal.NewFromUint64(event.BaseAmountOut),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountIn),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserve - event.BaseAmountOut),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserve + event.QuoteAmountIn),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
2026-02-27 02:07:52 +08:00
Cashback: isCashbackCoin,
2025-12-22 17:56:40 +08:00
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
2025-11-20 17:56:45 +08:00
EntryContract: entryContract,
},
}, offset, nil
}
2025-11-21 12:01:44 +08:00
func ammSellParser(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 prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
2025-12-22 17:56:40 +08:00
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
2025-12-31 16:53:39 +08:00
for _, innerInstr := range innerInstructions.Instructions {
2025-12-22 17:56:40 +08:00
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
entryContract = result.accountList[innerInstr.ProgramIDIndex]
2026-02-09 16:09:31 +08:00
break
2025-12-22 17:56:40 +08:00
}
}
}
2025-11-20 17:56:45 +08:00
var event ammSellEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmSellEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
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 amm sell pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammSellEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm sell event not found, offset: %d, %d", offset[0], prefixLen)
}
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
baseTokenProgram := result.accountList[instruction.Accounts[11]]
quoteTokenProgram := result.accountList[instruction.Accounts[12]]
poolBaseAccountIdx := instruction.Accounts[7]
poolQuoteAccountIdx := instruction.Accounts[8]
var (
baseMintDecimals uint8
quoteMintDecimals uint8
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
} else if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
}
}
2025-11-21 12:01:44 +08:00
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseTokenProgram,
}
}
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteTokenProgram,
}
}
2025-12-22 17:56:40 +08:00
var eventUser = event.User
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
2026-02-27 02:07:52 +08:00
isCashbackCoin := event.CashbackFeeBasisPoints > 0 || event.Cashback > 0
2025-11-20 17:56:45 +08:00
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "sell",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
2025-12-22 17:56:40 +08:00
User: eventUser,
2025-11-20 17:56:45 +08:00
BaseAmount: decimal.NewFromUint64(event.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountOut),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
2026-02-27 02:07:52 +08:00
Cashback: isCashbackCoin,
2025-12-22 17:56:40 +08:00
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
2025-11-20 17:56:45 +08:00
EntryContract: entryContract,
},
}, offset, nil
}
2025-11-21 12:01:44 +08:00
func depositParse(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 prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm deposit get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammDepositEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmDepositEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
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 amm deposit pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammDepositEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm deposit event not found, offset: %d, %d", offset[0], prefixLen)
}
var (
poolBaseAccountIdx = instruction.Accounts[9]
poolQuoteAccountIdx = instruction.Accounts[10]
baseMintDecimals uint8
quoteMintDecimals uint8
baseMintProgram solana.PublicKey
quoteMintProgram solana.PublicKey
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
baseMintProgram = meta.ProgramIDAccount
if baseMintProgram.IsZero() && meta.ProgramID != "" {
baseMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
quoteMintProgram = meta.ProgramIDAccount
if quoteMintProgram.IsZero() && meta.ProgramID != "" {
quoteMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
}
2025-11-21 12:01:44 +08:00
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseMintProgram,
}
}
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteMintProgram,
}
}
2025-11-20 17:56:45 +08:00
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "deposit",
Pool: event.Pool,
2025-11-21 12:01:44 +08:00
BaseMint: baseMint,
QuoteMint: quoteMint,
2025-11-20 17:56:45 +08:00
BaseTokenProgram: baseMintProgram,
QuoteTokenProgram: quoteMintProgram,
//Creator: solana.PublicKey{},
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(event.QuoteAmountIn),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves + event.QuoteAmountIn),
//Mayhem: false,
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves - event.BaseAmountIn),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves - event.QuoteAmountIn),
EntryContract: entryContract,
},
}, offset, nil
}
2025-11-21 12:01:44 +08:00
func withdrawParse(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 prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm withdraw get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
var event ammWithdrawEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
bytes.Equal(innerInstr.Data[:8], pumpAmmEventDiscriminator[:]) &&
bytes.Equal(innerInstr.Data[8:16], pumpAmmWithdrawEventDiscriminator[:]) {
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&event)
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 amm withdraw pool event decode error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
break
}
}
if event == (ammWithdrawEvent{}) {
return nil, increaseOffset(offset), fmt.Errorf("pump amm withdraw event not found, offset: %d, %d", offset[0], prefixLen)
}
var (
poolBaseAccountIdx = instruction.Accounts[9]
poolQuoteAccountIdx = instruction.Accounts[10]
baseMintDecimals uint8
quoteMintDecimals uint8
baseMintProgram solana.PublicKey
quoteMintProgram solana.PublicKey
)
for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == poolBaseAccountIdx {
baseMintDecimals = uint8(meta.UITokenAmount.Decimals)
baseMintProgram = meta.ProgramIDAccount
if baseMintProgram.IsZero() && meta.ProgramID != "" {
baseMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
if meta.AccountIndex == poolQuoteAccountIdx {
quoteMintDecimals = uint8(meta.UITokenAmount.Decimals)
quoteMintProgram = meta.ProgramIDAccount
if quoteMintProgram.IsZero() && meta.ProgramID != "" {
quoteMintProgram = solana.MustPublicKeyFromBase58(meta.ProgramID)
}
}
}
2025-11-21 12:01:44 +08:00
baseMint := result.accountList[instruction.Accounts[3]]
quoteMint := result.accountList[instruction.Accounts[4]]
if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) {
tx.Token[baseMint] = TokenMeta{
Mint: baseMint,
Decimals: baseMintDecimals,
TokenProgram: baseMintProgram,
}
}
2025-11-20 17:56:45 +08:00
2025-11-21 12:01:44 +08:00
if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) {
tx.Token[quoteMint] = TokenMeta{
Mint: quoteMint,
Decimals: quoteMintDecimals,
TokenProgram: quoteMintProgram,
}
}
2025-11-20 17:56:45 +08:00
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "withdraw",
Pool: event.Pool,
BaseMint: result.accountList[instruction.Accounts[3]],
QuoteMint: result.accountList[instruction.Accounts[4]],
BaseTokenProgram: baseMintProgram,
QuoteTokenProgram: quoteMintProgram,
//Creator: solana.PublicKey{},
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
BaseAmount: decimal.NewFromUint64(event.BaseAmountOut),
QuoteAmount: decimal.NewFromUint64(event.QuoteAmountOut),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves - event.BaseAmountOut),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
//Mayhem: false,
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves + event.BaseAmountOut),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves + event.QuoteAmountOut),
EntryContract: entryContract,
},
}, offset, nil
}