punm parser
This commit is contained in:
439
pump.go
Normal file
439
pump.go
Normal file
@@ -0,0 +1,439 @@
|
||||
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.
|
||||
func pumpParser(result *RawTx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
|
||||
if !result.accountList[instruction.ProgramIDIndex].Equals(pumpProgram) {
|
||||
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 {
|
||||
offset[1] += 1
|
||||
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 {
|
||||
case pumpBuyV2Discriminator, pumpBuyDiscriminator, pumpSellDiscriminator:
|
||||
return BuyOrSellParser(result, instruction, innerInstructions, offset)
|
||||
case pumpCreateDiscriminator, pumpCreateV2Discriminator:
|
||||
return CreateParser(result, instruction, innerInstructions, offset)
|
||||
case pumpMigrateDiscriminator:
|
||||
return MigrateParser(result, instruction, innerInstructions, offset)
|
||||
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
|
||||
}
|
||||
|
||||
func getInnerInstructions(innerInstructions InnerInstructions, offset uint) ([]Instruction, error) {
|
||||
var inners []Instruction
|
||||
var prefixLen = offset
|
||||
if prefixLen > uint(len(innerInstructions.Instructions)) {
|
||||
return nil, fmt.Errorf("error inner instruction index out of range")
|
||||
}
|
||||
if prefixLen == 0 {
|
||||
inners = innerInstructions.Instructions
|
||||
} else {
|
||||
inners = innerInstructions.Instructions[prefixLen:]
|
||||
}
|
||||
return inners, nil
|
||||
}
|
||||
|
||||
func CreateParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
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[:]) {
|
||||
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 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[:]) {
|
||||
userIndex = instr.Accounts[5]
|
||||
} else if bytes.HasPrefix(instr.Data, pumpCreateDiscriminator[:]) {
|
||||
userIndex = instr.Accounts[7]
|
||||
}
|
||||
userBase := getAccountBalanceAfterTx(result, userIndex)
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
|
||||
return []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: "create",
|
||||
Pool: createEvent.BondingCurve,
|
||||
BaseMint: createEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
BaseTokenProgram: createEvent.TokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: createEvent.User,
|
||||
BaseAmount: decimal.Zero,
|
||||
QuoteAmount: decimal.Zero,
|
||||
BaseReserve: decimal.NewFromUint64(createEvent.RealTokenReserves),
|
||||
QuoteReserve: decimal.Zero,
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
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
|
||||
}
|
||||
|
||||
type CompleteEvent struct {
|
||||
User solana.PublicKey
|
||||
Mint solana.PublicKey
|
||||
BondingCurve solana.PublicKey
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
func BuyOrSellParser(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 programIndex = instruction.ProgramIDIndex
|
||||
|
||||
var (
|
||||
tradeEvent PumpTradeEvent
|
||||
completeEvent CompleteEvent
|
||||
completed bool
|
||||
newoffset [2]uint
|
||||
)
|
||||
|
||||
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[:]) {
|
||||
if bytes.Equal(innerInstr.Data[8:16], pumpTradeEventDiscriminator[8:16]) {
|
||||
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&tradeEvent)
|
||||
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])
|
||||
}
|
||||
if !tradeEvent.IsBuy {
|
||||
break
|
||||
}
|
||||
} else if bytes.Equal(innerInstr.Data[8:16], pumpCompleteEventDiscriminator[:]) {
|
||||
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&completeEvent)
|
||||
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 completeEvent event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
completed = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if tradeEvent == (PumpTradeEvent{}) {
|
||||
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]}
|
||||
ataUserIdx := instruction.Accounts[5]
|
||||
userIndex := instruction.Accounts[6]
|
||||
|
||||
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
|
||||
event := ""
|
||||
baseTokenProgram := solana.TokenProgramID
|
||||
if tradeEvent.IsBuy {
|
||||
event = "buy"
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
||||
} else {
|
||||
event = "sell"
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
||||
}
|
||||
swaps := []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: event,
|
||||
Pool: result.accountList[instruction.Accounts[3]],
|
||||
BaseMint: tradeEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
Creator: tradeEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: tradeEvent.User,
|
||||
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(tradeEvent.SolAmount),
|
||||
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
||||
QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves),
|
||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}
|
||||
if completed {
|
||||
swaps = append(swaps, Swap{
|
||||
Program: SolProgramPump,
|
||||
Event: "complete",
|
||||
Pool: result.accountList[instruction.Accounts[3]],
|
||||
BaseMint: tradeEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
BaseTokenProgram: result.accountList[instruction.Accounts[8]],
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
Creator: tradeEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: tradeEvent.User,
|
||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
EntryContract: entryContract,
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func MigrateParser(result *RawTx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var err error
|
||||
programIndex := instr.ProgramIDIndex
|
||||
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
|
||||
userIndex := instr.Accounts[5]
|
||||
ataBondingCurveAccountIndex := instr.Accounts[4]
|
||||
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
|
||||
var userBase decimal.Decimal
|
||||
if result.accountList[userIndex].Equals(pumpMigrationAccount) {
|
||||
userBase = decimal.Zero
|
||||
} else {
|
||||
userBase = GetTokenBalanceAfterTx(result, userIndex, baseTokenProgram, migrateEvent.Mint)
|
||||
}
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
|
||||
swaps := []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: "migrate",
|
||||
Pool: migrateEvent.BondingCurve,
|
||||
BaseMint: migrateEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: migrateEvent.User,
|
||||
//BaseAmount: decimal.Decimal{},
|
||||
//QuoteAmount: decimal.Decimal{},
|
||||
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}
|
||||
swaps = append(swaps, Swap{
|
||||
Program: SolProgramPumpAMM,
|
||||
Event: "create",
|
||||
Pool: migrateEvent.Pool,
|
||||
BaseMint: migrateEvent.Mint,
|
||||
QuoteMint: wSolMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
User: migrateEvent.User,
|
||||
BaseAmount: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
EntryContract: entryContract,
|
||||
})
|
||||
|
||||
return swaps, offset, nil
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user