639 lines
22 KiB
Go
639 lines
22 KiB
Go
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(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
|
|
if !tx.rawTx.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 {
|
|
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:
|
|
if tx.Err != nil {
|
|
return failedTxBuyOrSellParser(tx, instruction, innerInstructions, offset)
|
|
}
|
|
return BuyOrSellParser(tx, instruction, innerInstructions, offset)
|
|
case pumpCreateDiscriminator, pumpCreateV2Discriminator:
|
|
if tx.Err != nil {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
return CreateParser(tx, instruction, innerInstructions, offset)
|
|
case pumpMigrateDiscriminator:
|
|
if tx.Err != nil {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
return MigrateParser(tx, 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
|
|
IsCashbackEnabled bool
|
|
}
|
|
|
|
func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
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)
|
|
|
|
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,
|
|
}
|
|
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,
|
|
Cashback: createEvent.IsCashbackEnabled,
|
|
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
|
|
|
|
CreatorFeeBasisPoints uint64
|
|
CreatorFee uint64
|
|
|
|
TrackVolume bool
|
|
TotalUnclaimedTokens uint64
|
|
TotalClaimedTokens uint64
|
|
CurrentSolVolume uint64
|
|
LastUpdateTimestamp int64
|
|
IxName string
|
|
MayhemMode bool
|
|
CashbackFeeBasisPoints uint64
|
|
Cashback uint64
|
|
}
|
|
|
|
type PumpTradeFeeArg struct {
|
|
IsPump bool
|
|
MarketCap [16]byte
|
|
TradeSize uint64
|
|
}
|
|
|
|
type CompleteEvent struct {
|
|
User solana.PublicKey
|
|
Mint solana.PublicKey
|
|
BondingCurve solana.PublicKey
|
|
Timestamp int64
|
|
}
|
|
|
|
type PumpTradeArgs struct {
|
|
Discriminator [8]byte
|
|
Amount1 uint64
|
|
Amount2 uint64
|
|
}
|
|
|
|
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 {
|
|
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]
|
|
|
|
user := result.accountList[instruction.Accounts[6]]
|
|
ataUserIdx := instruction.Accounts[5]
|
|
userIndex := instruction.Accounts[6]
|
|
mint := result.accountList[instruction.Accounts[2]]
|
|
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 (
|
|
solAmount, tokenAmount uint64
|
|
)
|
|
if bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]) {
|
|
event = "buy_failed"
|
|
solAmount = args.Amount1
|
|
tokenAmount = args.Amount2
|
|
} else if bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]) {
|
|
event = "buy_failed"
|
|
solAmount = args.Amount2
|
|
tokenAmount = args.Amount1
|
|
} else if bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]) {
|
|
event = "sell_failed"
|
|
solAmount = args.Amount2
|
|
tokenAmount = args.Amount1
|
|
} else {
|
|
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
var baseTokenProgram solana.PublicKey
|
|
|
|
if event == "buy_failed" {
|
|
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
|
} else {
|
|
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
|
}
|
|
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)
|
|
userQuote, _ := GetSolAfterTx(result, userIndex)
|
|
|
|
bcIdx := instruction.Accounts[3]
|
|
bcAtaIndex := instruction.Accounts[4]
|
|
solReserves, _ := GetSolAfterTx(result, bcIdx)
|
|
tokenReserves := getAccountBalanceAfterTx(result, bcAtaIndex)
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramPump,
|
|
Event: event,
|
|
Pool: result.accountList[instruction.Accounts[3]],
|
|
BaseMint: mint,
|
|
QuoteMint: solana.PublicKey{},
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: solana.PublicKey{},
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: 9,
|
|
User: user,
|
|
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
|
QuoteAmount: decimal.NewFromUint64(solAmount),
|
|
BaseReserve: tokenReserves,
|
|
QuoteReserve: decimal.NewFromUint64(solReserves),
|
|
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
return swaps, offset, nil
|
|
}
|
|
|
|
func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
|
var err error
|
|
var programIndex = instruction.ProgramIDIndex
|
|
|
|
feeEventProgramIndex := 0
|
|
for i, b := range result.accountList {
|
|
if b.Equals(pumpFeesProgram) {
|
|
feeEventProgramIndex = i
|
|
break
|
|
}
|
|
}
|
|
var (
|
|
tradeEvent PumpTradeEvent
|
|
tradeFeeArg PumpTradeFeeArg
|
|
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])
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex == feeEventProgramIndex && bytes.Equal(innerInstr.Data[:8], pumpGetFeesDiscriminator[:]) {
|
|
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
|
|
}
|
|
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]}
|
|
|
|
event := ""
|
|
baseTokenProgram := solana.TokenProgramID
|
|
if tradeEvent.IsBuy {
|
|
event = "buy"
|
|
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
|
} else {
|
|
event = "sell"
|
|
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
|
}
|
|
if _, exists := tx.Token[tradeEvent.Mint]; !exists {
|
|
tx.Token[tradeEvent.Mint] = TokenMeta{
|
|
Mint: tradeEvent.Mint,
|
|
TokenProgram: baseTokenProgram,
|
|
Decimals: 6,
|
|
}
|
|
}
|
|
|
|
var user = tradeEvent.User
|
|
|
|
ataUserIdx := instruction.Accounts[5]
|
|
userIndex := instruction.Accounts[6]
|
|
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)
|
|
userQuote, _ := GetSolAfterTx(result, userIndex)
|
|
|
|
solAmount := tradeEvent.SolAmount
|
|
if tradeEvent.IsBuy && bytes.Equal(instruction.Data[:8], pumpBuyV2Discriminator[:]) {
|
|
fee := tradeEvent.Fee + tradeEvent.CreatorFee
|
|
solAmount = tradeFeeArg.TradeSize
|
|
if solAmount > fee {
|
|
solAmount = solAmount - fee
|
|
}
|
|
}
|
|
isCashbackCoin := tradeEvent.CashbackFeeBasisPoints > 0 || tradeEvent.Cashback > 0
|
|
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: user,
|
|
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
|
|
QuoteAmount: decimal.NewFromUint64(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,
|
|
Cashback: isCashbackCoin,
|
|
},
|
|
}
|
|
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: user,
|
|
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,
|
|
})
|
|
}
|
|
|
|
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(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
|
result := tx.rawTx
|
|
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)
|
|
|
|
if _, exists := tx.Token[migrateEvent.Mint]; !exists {
|
|
tx.Token[migrateEvent.Mint] = TokenMeta{
|
|
Mint: migrateEvent.Mint,
|
|
TokenProgram: baseTokenProgram,
|
|
Decimals: 6,
|
|
}
|
|
}
|
|
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,
|
|
MigrateTopProgram: pumpAmmProgram,
|
|
MigrateToPool: migrateEvent.Pool,
|
|
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
|
|
|
|
}
|