1193 lines
40 KiB
Go
1193 lines
40 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 pumpBuyExactSolInDiscriminator, pumpBuyDiscriminator, pumpBuyV2Discriminator, pumpBuyExactQuoteInV2Discriminator, pumpSellDiscriminator, pumpSellV2Discriminator:
|
|
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, pumpMigrateV2Discriminator:
|
|
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
|
|
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
|
|
}
|
|
|
|
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[:]) {
|
|
createEvent, err = decodePumpCreateEvent(innerInstr.Data[16:])
|
|
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[:]) {
|
|
if len(instr.Accounts) < 6 {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
userIndex = instr.Accounts[5]
|
|
} else if bytes.HasPrefix(instr.Data, pumpCreateDiscriminator[:]) {
|
|
if len(instr.Accounts) < 8 {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
userIndex = instr.Accounts[7]
|
|
} else {
|
|
return nil, increaseOffset(offset), InstructionIgnoredError
|
|
}
|
|
userBase := getAccountBalanceAfterTx(result, userIndex)
|
|
userQuote, _ := GetSolAfterTx(result, userIndex)
|
|
quoteMint, quoteTokenProgram, quoteDecimals := pumpCreateQuoteAccounts(result, instr, createEvent)
|
|
userQuoteBalance := decimal.NewFromUint64(userQuote)
|
|
if !quoteMint.IsZero() && !quoteMint.Equals(wSolMint) {
|
|
userQuoteBalance = GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
|
|
}
|
|
|
|
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: quoteMint,
|
|
BaseTokenProgram: createEvent.TokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: createEvent.Creator,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
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: userQuoteBalance,
|
|
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
|
|
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
|
|
}
|
|
|
|
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 pumpTradeAmountInfoFromArgs(args PumpTradeArgs) (swapMode SwapMode, fixedAmount decimal.Decimal, limitAmount decimal.Decimal, ok bool) {
|
|
switch {
|
|
case bytes.Equal(args.Discriminator[:], pumpBuyExactSolInDiscriminator[:]),
|
|
bytes.Equal(args.Discriminator[:], pumpBuyExactQuoteInV2Discriminator[:]):
|
|
return SwapModeExactIn, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
|
case bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]),
|
|
bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]):
|
|
return SwapModeExactOut, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
|
case bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]),
|
|
bytes.Equal(args.Discriminator[:], pumpSellV2Discriminator[:]):
|
|
return SwapModeExactIn, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
|
default:
|
|
return SwapModeUnknown, decimal.Zero, decimal.Zero, false
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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]
|
|
|
|
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)
|
|
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 (
|
|
quoteAmount, tokenAmount uint64
|
|
)
|
|
if bytes.Equal(args.Discriminator[:], pumpBuyExactSolInDiscriminator[:]) ||
|
|
bytes.Equal(args.Discriminator[:], pumpBuyExactQuoteInV2Discriminator[:]) {
|
|
event = "buy_failed"
|
|
quoteAmount = args.Amount1
|
|
tokenAmount = args.Amount2
|
|
} else if bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]) ||
|
|
bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]) {
|
|
event = "buy_failed"
|
|
quoteAmount = args.Amount2
|
|
tokenAmount = args.Amount1
|
|
} else if bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]) ||
|
|
bytes.Equal(args.Discriminator[:], pumpSellV2Discriminator[:]) {
|
|
event = "sell_failed"
|
|
quoteAmount = args.Amount2
|
|
tokenAmount = args.Amount1
|
|
} else {
|
|
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
|
}
|
|
baseTokenProgram := pumpAccount(result, instruction, layout.BaseTokenProgram)
|
|
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 := decimal.Zero
|
|
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
|
userQuote = getAccountBalanceAfterTx(result, instruction.Accounts[layout.QuoteUserToken])
|
|
} else {
|
|
userQuoteLamports, _ := GetSolAfterTx(result, userIndex)
|
|
userQuote = decimal.NewFromUint64(userQuoteLamports)
|
|
}
|
|
|
|
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)
|
|
}
|
|
tokenReserves := getAccountBalanceAfterTx(result, bcAtaIndex)
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramPump,
|
|
Event: event,
|
|
Pool: pumpAccount(result, instruction, layout.Pool),
|
|
BaseMint: mint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
|
User: user,
|
|
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
|
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
|
BaseReserve: tokenReserves,
|
|
QuoteReserve: quoteReserves,
|
|
Mayhem: isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
if swapMode, fixedAmount, limitAmount, ok := pumpTradeAmountInfoFromArgs(args); ok {
|
|
swaps[0].SetSwapAmountInfo(swapMode, fixedAmount, limitAmount)
|
|
normalizePumpQuoteSideMint(&swaps[0])
|
|
}
|
|
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
|
|
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])
|
|
}
|
|
|
|
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
|
|
tradeFound bool
|
|
)
|
|
|
|
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 !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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for innerIndex, innerInstr := range inners {
|
|
if innerInstr.ProgramIDIndex == feeEventProgramIndex && bytes.Equal(innerInstr.Data[:8], pumpGetFeesDiscriminator[:]) {
|
|
if tradeFound {
|
|
continue
|
|
}
|
|
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]) {
|
|
if tradeFound {
|
|
break
|
|
}
|
|
tradeEvent, err = decodePumpTradeEvent(innerInstr.Data[16:])
|
|
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])
|
|
}
|
|
expectedIsBuy := !pumpInstructionIsSell(instruction.Data)
|
|
if tradeEvent.IsBuy != expectedIsBuy {
|
|
tradeEvent = PumpTradeEvent{}
|
|
continue
|
|
}
|
|
tradeFound = true
|
|
if !tradeEvent.IsBuy {
|
|
break
|
|
}
|
|
} else if bytes.Equal(innerInstr.Data[8:16], pumpCompleteEventDiscriminator[:]) {
|
|
if !tradeFound {
|
|
continue
|
|
}
|
|
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&completeEvent)
|
|
if err != nil {
|
|
return nil, increaseOffset(offset), fmt.Errorf("pump completeEvent event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
|
}
|
|
if !pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, pumpAccount(result, instruction, layout.Pool)) {
|
|
break
|
|
}
|
|
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
|
|
}
|
|
|
|
}
|
|
}
|
|
if !tradeFound {
|
|
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]}
|
|
|
|
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])
|
|
}
|
|
|
|
event := ""
|
|
baseTokenProgram := pumpAccount(result, instruction, layout.BaseTokenProgram)
|
|
quoteMint := tradeEvent.QuoteMint
|
|
if quoteMint.IsZero() {
|
|
quoteMint = pumpAccount(result, instruction, layout.QuoteMint)
|
|
}
|
|
quoteTokenProgram := pumpAccount(result, instruction, layout.QuoteTokenProgram)
|
|
if tradeEvent.IsBuy {
|
|
event = "buy"
|
|
} else {
|
|
event = "sell"
|
|
}
|
|
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[layout.BaseUserToken]
|
|
userIndex := instruction.Accounts[layout.User]
|
|
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 := decimal.Zero
|
|
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
|
userQuote = getAccountBalanceAfterTx(result, instruction.Accounts[layout.QuoteUserToken])
|
|
} else {
|
|
userQuoteLamports, _ := GetSolAfterTx(result, userIndex)
|
|
userQuote = decimal.NewFromUint64(userQuoteLamports)
|
|
}
|
|
|
|
quoteAmount := pumpQuoteAmount(tradeEvent)
|
|
if tradeEvent.IsBuy && pumpInstructionIsExactQuoteIn(instruction.Data) && !layout.IsV2 {
|
|
fee := tradeEvent.Fee + tradeEvent.CreatorFee
|
|
quoteAmount = tradeFeeArg.TradeSize
|
|
if quoteAmount > fee {
|
|
quoteAmount = quoteAmount - fee
|
|
}
|
|
}
|
|
isCashbackCoin := tradeEvent.CashbackFeeBasisPoints > 0 || tradeEvent.Cashback > 0
|
|
swaps := []Swap{
|
|
{
|
|
Program: SolProgramPump,
|
|
Event: event,
|
|
Pool: pumpAccount(result, instruction, layout.Pool),
|
|
BaseMint: tradeEvent.Mint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: tradeEvent.Creator,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
|
User: user,
|
|
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
|
|
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
|
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
|
QuoteReserve: decimal.NewFromUint64(pumpQuoteReserve(tradeEvent)),
|
|
Mayhem: tradeEvent.MayhemMode || isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
Cashback: isCashbackCoin,
|
|
},
|
|
}
|
|
if swapMode, fixedAmount, limitAmount, ok := pumpTradeAmountInfoFromArgs(args); ok {
|
|
swaps[0].SetSwapAmountInfo(swapMode, fixedAmount, limitAmount)
|
|
normalizePumpQuoteSideMint(&swaps[0])
|
|
}
|
|
if completed {
|
|
swaps = append(swaps, Swap{
|
|
Program: SolProgramPump,
|
|
Event: "complete",
|
|
Pool: pumpAccount(result, instruction, layout.Pool),
|
|
BaseMint: tradeEvent.Mint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: tradeEvent.Creator,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
|
User: user,
|
|
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
|
QuoteReserve: decimal.NewFromUint64(pumpQuoteReserve(tradeEvent)),
|
|
Mayhem: tradeEvent.MayhemMode || isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: 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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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
|
|
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])
|
|
}
|
|
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[layout.User]
|
|
ataBondingCurveAccountIndex := instr.Accounts[layout.BasePoolToken]
|
|
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
|
|
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)
|
|
}
|
|
var userBase decimal.Decimal
|
|
if result.accountList[userIndex].Equals(pumpMigrationAccount) {
|
|
userBase = decimal.Zero
|
|
} else {
|
|
userBase = GetTokenBalanceAfterTx(result, userIndex, baseTokenProgram, migrateEvent.Mint)
|
|
}
|
|
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)
|
|
|
|
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: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: createEvent.Creator,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: migrateEvent.User,
|
|
//BaseAmount: decimal.Decimal{},
|
|
//QuoteAmount: decimal.Decimal{},
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
Mayhem: createEvent.IsMayhemMode,
|
|
MigrateTopProgram: pumpAmmProgram,
|
|
MigrateToPool: migrateEvent.Pool,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
},
|
|
}
|
|
swaps = append(swaps, Swap{
|
|
Program: SolProgramPumpAMM,
|
|
Event: "create",
|
|
Pool: migrateEvent.Pool,
|
|
BaseMint: migrateEvent.Mint,
|
|
QuoteMint: quoteMint,
|
|
BaseTokenProgram: baseTokenProgram,
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
Creator: createEvent.Creator,
|
|
BaseMintDecimals: 6,
|
|
QuoteMintDecimals: quoteDecimals,
|
|
User: migrateEvent.User,
|
|
BaseAmount: baseAmount,
|
|
QuoteAmount: quoteAmount,
|
|
BaseReserve: baseReserve,
|
|
QuoteReserve: quoteReserve,
|
|
Mayhem: createEvent.IsMayhemMode,
|
|
UserBaseBalance: userBase,
|
|
UserQuoteBalance: userQuote,
|
|
EntryContract: entryContract,
|
|
})
|
|
|
|
return swaps, offset, nil
|
|
|
|
}
|