Files
pump-parser/pumpamm.go

514 lines
20 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"
)
type ammBuyEvent struct {
TimeStamp int64
BaseAmountOut uint64
MaxQuoteAmountIn uint64
UserBaseTokenReserve uint64
UserQuoteTokenReserve uint64
PoolBaseTokenReserve uint64
PoolQuoteTokenReserve uint64
QuoteAmountIn uint64
LpFeeBasisPoints uint64
LpFee uint64
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
}
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
}
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
}
func pumpAmmParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
if !result.accountList[instruction.ProgramIDIndex].Equals(pumpAmmProgram) {
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:
return ammCreatePoolParser(result, instruction, innerInstructions, offset)
case pumpAmmBuyDiscriminator:
return ammBuyParser(result, instruction, innerInstructions, offset)
case pumpAmmSellDiscriminator:
return ammSellParser(result, instruction, innerInstructions, offset)
case pumpAmmDepositDiscriminator:
return depositParse(result, instruction, innerInstructions, offset)
case pumpAmmWithdrawDiscriminator:
return withdrawParse(result, instruction, innerInstructions, offset)
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
func ammCreatePoolParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
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 {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
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]]
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
}
func ammBuyParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
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 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 {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
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)
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "buy",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
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]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserve + event.BaseAmountOut),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserve - event.UserQuoteAmountIn),
EntryContract: entryContract,
},
}, offset, nil
}
func ammSellParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
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)
}
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 {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
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)
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "sell",
Pool: event.Pool,
BaseMint: baseMint,
QuoteMint: quoteMint,
BaseTokenProgram: baseTokenProgram,
QuoteTokenProgram: quoteTokenProgram,
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
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]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves - event.BaseAmountIn),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves + event.UserQuoteAmountOut),
EntryContract: entryContract,
},
}, offset, nil
}
func depositParse(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
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 {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
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)
}
}
}
return []Swap{
{
Program: SolProgramPumpAMM,
Event: "deposit",
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.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
}
func withdrawParse(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
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 {
offset[1] += uint(innerIndex) + 1 + prefixLen
}
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)
}
}
}
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
}